diff --git a/spring-security-modules/spring-security-pkce/.gitignore b/spring-security-modules/spring-security-pkce/.gitignore
new file mode 100644
index 0000000000..549e00a2a9
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-security-modules/spring-security-pkce/client/pom.xml b/spring-security-modules/spring-security-pkce/client/pom.xml
new file mode 100644
index 0000000000..6961167716
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/client/pom.xml
@@ -0,0 +1,24 @@
+
+ 4.0.0
+
+ com.baeldung
+ spring-security-pkce
+ 0.0.1-SNAPSHOT
+
+ pkce-client
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java
new file mode 100644
index 0000000000..2a23b32fc8
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/PkceClientApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.security.pkce.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class PkceClientApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PkceClientApplication.class, args);
+ }
+
+}
diff --git a/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java
new file mode 100644
index 0000000000..6863c13fa7
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/client/src/main/java/com/baeldung/security/pkce/client/config/OAuth2ClientConfiguration.java
@@ -0,0 +1,30 @@
+package com.baeldung.security.pkce.client.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers;
+import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver;
+import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+@Configuration
+public class OAuth2ClientConfiguration {
+
+ @Bean
+ public SecurityWebFilterChain pkceFilterChain(ServerHttpSecurity http, ServerOAuth2AuthorizationRequestResolver resolver) {
+ http.authorizeExchange(r -> r.anyExchange().authenticated());
+ http.oauth2Login(auth -> auth.authorizationRequestResolver(resolver));
+ return http.build();
+ }
+
+ @Bean
+ public ServerOAuth2AuthorizationRequestResolver pkceResolver(ReactiveClientRegistrationRepository repo) {
+ var resolver = new DefaultServerOAuth2AuthorizationRequestResolver(repo);
+ resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
+ return resolver;
+ }
+
+
+}
diff --git a/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml b/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml
new file mode 100644
index 0000000000..544f8254eb
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/client/src/main/resources/application.yaml
@@ -0,0 +1,13 @@
+spring:
+ security:
+ oauth2:
+ client:
+ provider:
+ baeldung:
+ issuer-uri: http://localhost:8085
+ registration:
+ pkce:
+ provider: baeldung
+ client-id: pkce-client
+ client-secret: obscura
+ scope: openid,email
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html b/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html
new file mode 100644
index 0000000000..9f2232ae9c
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/client/src/main/resources/static/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+Baeldung :: PKCE-Client
+
+
+ Hello from PKCE-Client
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-pkce/pom.xml b/spring-security-modules/spring-security-pkce/pom.xml
new file mode 100644
index 0000000000..7631d54354
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+ spring-security-pkce
+ pom
+ spring-security-pkce
+ Demo project for Spring Boot
+
+
+ 2.7.2
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
+ server
+ client
+
+
diff --git a/spring-security-modules/spring-security-pkce/server/pom.xml b/spring-security-modules/spring-security-pkce/server/pom.xml
new file mode 100644
index 0000000000..973b29f473
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/pom.xml
@@ -0,0 +1,25 @@
+
+ 4.0.0
+
+ com.baeldung
+ spring-security-pkce
+ 0.0.1-SNAPSHOT
+
+ pkce-auth-server
+
+
+ 0.3.1
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.security
+ spring-security-oauth2-authorization-server
+ ${spring-authorization-server.version}
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java
new file mode 100644
index 0000000000..e568743838
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/PkceAuthServerApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.security.pkce.authserver;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class PkceAuthServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PkceAuthServerApplication.class, args);
+ }
+
+}
diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java
new file mode 100644
index 0000000000..1f6484478d
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/AuthServerConfiguration.java
@@ -0,0 +1,99 @@
+package com.baeldung.security.pkce.authserver.conf;
+
+import java.util.UUID;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.crypto.password.NoOpPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+@Configuration
+public class AuthServerConfiguration {
+
+ @Bean
+ @Order(1)
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+
+ var authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
+ var endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
+
+ // @formatter:off
+ http
+ .requestMatcher(endpointsMatcher)
+ .authorizeRequests(authorize ->
+ authorize
+ .anyRequest()
+ .authenticated());
+ http
+ .exceptionHandling(exceptions ->
+ exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
+ .csrf( csrf ->
+ csrf
+ .ignoringRequestMatchers(endpointsMatcher))
+ .apply(authorizationServerConfigurer);
+
+ // Required by /userinfo endpoint
+ http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
+ return http.build();
+ // @formatter:on
+ }
+
+ @Bean
+ @Order(2)
+ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
+ // @formatter:off
+ return http
+ .formLogin(Customizer.withDefaults())
+ .build();
+ // @formatter:on
+ }
+
+ @Bean
+ public RegisteredClientRepository registeredClientRepository() {
+
+ var pkceClient = RegisteredClient
+ .withId(UUID.randomUUID().toString())
+ .clientId("pkce-client")
+ .clientSecret("{noop}obscura")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.EMAIL)
+ .scope(OidcScopes.PROFILE)
+ .clientSettings(ClientSettings.builder()
+ .requireAuthorizationConsent(false)
+ .requireProofKey(true)
+ .build())
+ .redirectUri("http://127.0.0.1:8080/login/oauth2/code/pkce") // Localhost not allowed
+ .build();
+
+ return new InMemoryRegisteredClientRepository(pkceClient);
+ }
+
+ @Bean
+ public ProviderSettings providerSettings() {
+ return ProviderSettings
+ .builder()
+ .build();
+ }
+
+}
diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java
new file mode 100644
index 0000000000..a253141101
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/JwksConfiguration.java
@@ -0,0 +1,53 @@
+package com.baeldung.security.pkce.authserver.conf;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.UUID;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+
+@Configuration
+public class JwksConfiguration {
+
+ @Bean
+ public JwtDecoder jwtDecoder(JWKSource jwkSource) {
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
+ }
+
+ @Bean
+ public JWKSource jwkSource() {
+ KeyPair keyPair = generateRsaKey();
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+ RSAKey rsaKey = new RSAKey.Builder(publicKey)
+ .privateKey(privateKey)
+ .keyID(UUID.randomUUID().toString())
+ .build();
+ JWKSet jwkSet = new JWKSet(rsaKey);
+ return new ImmutableJWKSet<>(jwkSet);
+ }
+
+ private static KeyPair generateRsaKey() {
+ KeyPair keyPair;
+ try {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ keyPair = keyPairGenerator.generateKeyPair();
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ return keyPair;
+ }
+}
diff --git a/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java
new file mode 100644
index 0000000000..6877524508
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/main/java/com/baeldung/security/pkce/authserver/conf/UserDetailsConfiguration.java
@@ -0,0 +1,26 @@
+package com.baeldung.security.pkce.authserver.conf;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+public class UserDetailsConfiguration {
+
+ @Bean
+ public UserDetailsService userDetailsService() {
+ // @formatter:off
+ UserDetails userDetails = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(userDetails);
+ // @formatter:on
+ }
+
+}
diff --git a/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties b/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties
new file mode 100644
index 0000000000..cd2d02be7c
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=8085
diff --git a/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java b/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java
new file mode 100644
index 0000000000..1a91420808
--- /dev/null
+++ b/spring-security-modules/spring-security-pkce/server/src/test/java/com/baeldung/security/pkce/SpringSecurityPkceApplicationTests.java
@@ -0,0 +1,13 @@
+package com.baeldung.security.pkce;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringSecurityPkceApplicationLiveTest {
+
+ @Test
+ void whenContextLoads_thenSuccess() {
+ }
+
+}