Authorization Code Flow with Proof Key for Code Exchange (PKCE)
Tags: java, security, oauth2, springsecurity
Published: 2023-03-03 01:25:38


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs7nxrnoz87989d2hlzm.png)

## Overview

OAuth2 divides clients into two types according to whether they can hold the client key: **public clients** and **confidential clients**.

The **confidential client** runs on the server, and the application created by Spring Boot in the previous OAuth2 article is an example of a confidential client type. First, they run on servers, often behind firewalls or gateways with other protections.

The code of the **public client** is generally exposed to the end user in some form, either downloaded and executed in the browser, or directly run on the user's device. For example, a **native application** is an application that runs directly on the end user's device (computer or mobile device). When this type of application uses the OAuth2 protocol, we cannot guarantee that the client key issued for this application can be stored securely, because these applications will be fully downloaded to the device before running, and decompiling the application will fully reveal the client key.

The same security issue exists with **single page applications** (SPA), the browser itself is an insecure environment, once you load a JavaScript application, the browser will download the entire source code in order to run it, the entire source code, including the Any client secrets will be visible. If you build an application with 100,000 users, chances are that some of those users will get malware or a virus and leak client keys.

You might be thinking, "What if I obfuscate the client key by splitting it into parts?" This will undeniably buy you some time, but a really determined person might still figure it out.

To circumvent this security risk, it is best to use Proof Key for Code Exchange (PKCE).

## Proof Key for Code Exchange

PKCE has its own independent [specification](https://tools.ietf.org/html/rfc7636). It enables applications to use the authorization code flow in public clients.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shr29bqgcvargi5e70n6.png)


1. A user requests a resource on the client side.
2. The client creates and records the secret information named code_verifier, and then the client calculates code_challenge based on code_verifier. Its value can be code_verifier or the SHA-256 hash of code_verifier, but cryptographic hashing should be preferred,because it prevents the authenticator itself from being intercepted.
3. The client sends the code_challenge and optional code_challenge_method (a keyword representing the original text or SHA-256 hash) to the authorization server along with the normal authorization request parameters.
4. The authorization server redirects the user to the login page.
5. The user is authenticated and may see a consent page listing the permissions the authorization server will grant to the client.
6. The authorization server records the code_challenge and code_challenge_method (if any). The authorization server will associate this information with the issued authorization code, and redirect back to the client with the code.
7. After receiving the authorization code, the client carries the previously generated code_verifier to execute the token request.
8. The authorization server calculates the code_challenge based on the code_verifier and checks whether it is consistent with the originally submitted code_challenge.
9. Authorization server sends token to client.
10. The client requests the protected resource using the token.
11. A protected resource returns a resource to the client.

> 💡 Note: If you don’t want to read till the end, you can view the [source code](https://github.com/ReLive27/spring-security-oauth2-sample/tree/main/oauth2-pkce) here.Don’t forget to give a star to the project if you like it!

## Use Spring Authorization Server to build an authorization server &#x1F680;

In this section, we will use [Spring Authorization Server](https://spring.io/projects/spring-authorization-server) to build an authorization server and register a client to support PKCE.

### Maven

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-authorization-server</artifactId>
  <version>0.3.1</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
```

### Configuration

The first is very simple, we will create the `application.yml` file and specify the authorization server port as 8080:

```yaml
server:
  port: 8080
```

Afterwards we will create an `OAuth2ServerConfig` configuration class, and within this class we will create the specific beans required for the OAuth2 authorization service:

```java
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
  OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
  return http.exceptionHandling(exceptions -> exceptions.
                                authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).build();
}

@Bean
public RegisteredClientRepository registeredClientRepository() {
  RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("relive-client")
    .clientAuthenticationMethods(s -> {
      s.add(ClientAuthenticationMethod.NONE);//Client authentication mode is none
    })
    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
    .redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-pkce")
    .scope("message.read")
    .clientSettings(ClientSettings.builder()
                    .requireAuthorizationConsent(true)
                    .requireProofKey(true) //Only PKCE is supported
                    .build())
    .tokenSettings(TokenSettings.builder()
                   .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // Generate JWT token
                   .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                   .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
                   .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
                   .reuseRefreshTokens(true)
                   .build())
    .build();

  return new InMemoryRegisteredClientRepository(registeredClient);
}

@Bean
public ProviderSettings providerSettings() {
  return ProviderSettings.builder()
    .issuer("http://127.0.0.1:8080")
    .build();
}

@Bean
public JWKSource<SecurityContext> jwkSource() {
  RSAKey rsaKey = Jwks.generateRsa();
  JWKSet jwkSet = new JWKSet(rsaKey);
  return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

static class Jwks {

  private Jwks() {
  }

  public static RSAKey generateRsa() {
    KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    return new RSAKey.Builder(publicKey)
      .privateKey(privateKey)
      .keyID(UUID.randomUUID().toString())
      .build();
  }
}

static class KeyGeneratorUtils {

  private KeyGeneratorUtils() {
  }

  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;
  }
}
```

When creating the `RegisteredClient` client registration class:

1. We did not define `client_secret`.

2.The client authentication mode is specified as none.

3.requireProofKey() is set to true, this client only supports PKCE.


I will not explain the rest of the configuration here, you can refer to the [previous article](https://dev.to/relive27/using-jwt-with-spring-security-oauth2-41jp).



Next, we create a Spring Security configuration class, specify Form form authentication and set username and password:

```java
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .formLogin(withDefaults());
        return http.build();
    }

    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

```


So far we have configured a simple authorization server. &#x1F600;

## OAuth2.0 client &#x1F680;

In this section, we use [Spring Security](https://docs.spring.io/spring-security/reference/servlet/oauth2/client/index.html) to create a client that requests authorization from the authorization server through the PKCE authorization code flow, and sends the obtained access_token to the resource service.

### Maven

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webflux</artifactId>
  <version>5.3.9</version>
</dependency>
<dependency>
  <groupId>io.projectreactor.netty</groupId>
  <artifactId>reactor-netty</artifactId>
  <version>1.0.9</version>
</dependency>

```

### Configuration

First, we will configure the client information in `application.yml`, and specify the service port number as 8070:

```yaml
server:
  port: 8070
  servlet:
    session:
      cookie:
        name: CLIENT-SESSION

spring:
  security:
    oauth2:
      client:
        registration:
          messaging-client-pkce:
            provider: client-provider
            client-id: relive-client
            client-secret: relive-client
            authorization-grant-type: authorization_code
            client-authentication-method: none
            redirect-uri: "http://127.0.0.1:8070/login/oauth2/code/{registrationId}"
            scope: message.read
            client-name: messaging-client-pkce
        provider:
          client-provider:
            authorization-uri: http://127.0.0.1:8080/oauth2/authorize
            token-uri: http://127.0.0.1:8080/oauth2/token

```

Next, we create the Spring Security configuration class to enable the OAuth2 client.

```java
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        //It is convenient for us to test, and the client service does not add authentication
                        authorizeRequests.anyRequest().permitAll()
                )
                .oauth2Client(withDefaults());
        return http.build();
    }

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .filter(oauth2Client)
                .build();
    }

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                          OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
                .builder()
                .authorizationCode()
                .refreshToken()
                .build();
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }
}

```

In the above configuration class, we enable the OAuth2 client through *oauth2Client(withDefaults())*. And create a `WebClient` instance to perform HTTP requests to the resource server. `OAuth2AuthorizedClientManager` is a high-level controller class that coordinates OAuth2 authorization code requests, but the authorization code process is not controlled by it. There is no relevant authorized code process logic in `AuthorizationCodeOAuth2AuthorizedClientProvider` class, for the core interface process for Spring Secrity authorization code mode, I will introduce it in a later article. 

Going back to the `OAuth2AuthorizedClientManager` class, we can see that *refreshToken()* is also specified at the same time, which implements the refresh token logic and will refresh the token after the access_token expires in the process of requesting resource services, the premise is that the refresh_token has not expired, otherwise You will go through the OAuth2 authorization code flow again.

Next, we create a Controller class and use WebClient to request resource services:

```java
@RestController
public class PkceClientController {

    @Autowired
    private WebClient webClient;

    @GetMapping(value = "/client/test")
    public List getArticles(@RegisteredOAuth2AuthorizedClient("messaging-client-pkce") OAuth2AuthorizedClient authorizedClient) {
        return this.webClient
                .get()
                .uri("http://127.0.0.1:8090/resource/article")
                .attributes(oauth2AuthorizedClient(authorizedClient))
                .retrieve()
                .bodyToMono(List.class)
                .block();
    }
}
```

## Resource Server &#x1F680;

In this section, we will use Spring Security to build a resource server.

### Maven

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  <version>2.6.7</version>
</dependency>
```

### Configuration

Configure the resource server service port 8070 through `application.yml`, and specify the authorization server jwk uri, which is used to obtain the public key information and verify the token:

```yaml
server:
  port: 8090

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
```

Next configure the Spring Security configuration class to specify access to protected endpoints:

```java
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain defaultSecurityFilter(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/resource/article")
                .and()
                .authorizeHttpRequests((authorize) -> authorize
                        .antMatchers("/resource/article")
                        .hasAuthority("SCOPE_message.read")
                        .mvcMatchers()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }
}
```

In the above configuration class, it is specified that */resource/article* must have the message.read permission to access, and the resource service is configured to use JWT authentication.


Afterwards we will create the Controller class, creating protected test endpoints:

```java
@RestController
public class ArticleRestController {

    @GetMapping("/resource/article")
    public List<String> article() {
        return Arrays.asList("article1", "article2", "article3");
    }
}
```

## Access Resource List

After starting all the services, enter [http://127.0.0.1:8070/client/test](http://127.0.0.1:8070/client/test) in the browser, after passing the authorization server authentication, you will see the following output information on the page:


```json
["article1","article2","article3"]
```


## Conclusion

Confidential client-side PKCE has become the default behavior in the current version of Spring Security. PKCE can also be used in secure client authorization code mode.

The source code used in this article is available on [GitHub](https://github.com/ReLive27/spring-security-oauth2-sample/tree/main/oauth2-pkce).

You might want to read on to the next one:
- [Spring Security OAuth2 Login](https://dev.to/relive27/spring-security-oauth2-login-51lj)


Thanks for reading!  &#x1F618;