Spring boot + Spring Security 5 + OAuth2/OIDC Client - Deep Dive
Tags: oauth2, java, spring, springsecurity
Published: 2019-03-25 21:51:04

---
title: Spring boot + Spring Security 5 + OAuth2/OIDC Client - Deep Dive
published: true
description: Decoding magic behind spring boot oauth2 client 
cover_image: https://thepracticaldev.s3.amazonaws.com/i/g0to3qrlwoi1hjrmacna.jpg
tags: #oauth2 #java #spring #springsecurity

---

In my [previous post](https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo) we saw how easy it is to protect your application with Google Login.

Now let us see what are all the components responsible for this to work.


# The Login process

## Request to access the protected Endpoint and Google Authentication process starts

**Prerequisites**
1. `application.yml` is [configured](https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo#step-3) with client and provider values
2. Provider name in the property `spring.security.oauth2.client.registration.provider` is set to google


When access to `http://localhost:8080/me` is requested, If you have only one identity provider configured then, Spring redirects you automatically to `http://localhost:8080/oauth2/authorization/google`. `OAuth2AuthorizationRequestRedirectFilter ` which is registered to the url pattern `/oauth2/authorization/*` will load the respective configuration and redirect to the Identity Provider. In our case Google.

> If you want your users to choose between multiple providers, then configure your `application.yml` for multiple providers, but you need a login page where you can have multiple links for the users to choose from.

## On Successful authentication google redirects to the app's redirect url

Once the user authenticates with Google successfully, Google now redirects to the app's redirect url configured in Google's developer console. In our example we chose to have a particular url `http://localhost:8080/login/oauth2/code/google`. This is because the Authentication Processing filter for OAuth2 `OAuth2LoginAuthenticationFilter` is registered to listen to `/login/oauth2/code/*`. 

OAuth2LoginAuthenticationFilter delegates authentication to `OidcAuthorizationCodeAuthenticationProvider` which does 3 things:

- Exchanges Code for token
- Validates id_token
- Populates User Info by calling the User Info endpoint, from Google's well known configuration
    

Now you might ask what if I have registered a different redirect URI, and want OAuth2LoginAuthenticationFilter to listen to this. It is pretty simple all you need to do is have the following Security Configuration

```kotlin
@Configuration
@EnableWebSecurity
class SecurityConfiguration: WebSecurityConfigurerAdapter() {
    override fun configure(http: HttpSecurity) {
        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login()
                .redirectionEndpoint()
                .baseUri("/oauth/callback/*")
  
    }
}
```

## What Next

As a result of successful authentication, you will get an Authentication object of type `OAuth2AuthenticationToken`. This token will contain all the necessary information from `id_token` and the `user info endpoint`.

You can access all data about the logged in user  by 

```kotlin
SecurityContextHolder.getContext().authentication as OAuth2AuthenticationToken
```

or 

```kotlin
 @GetMapping("/me")
 fun hello(currentUser: OAuth2AuthenticationToken): ResponseEntity<OAuth2AuthenticationToken> {
        return ResponseEntity.ok(currentUser)
}
```


## But what about Access and Refresh Tokens?

As a result of successful OpenID Connect flow, a client application receives three tokens, `access_token, refresh_token and id_token`. We might want to use this access token to access some protected resource from a resource server [like tasks API of google](https://developers.google.com/tasks/v1/reference/tasks/list). The `OAuth2AuthorizedClientService` keeps track of the tokens associated with the user.

```kotlin
 val currentUser = SecurityContextHolder.getContext().authentication as OAuth2AuthenticationToken
 val currentUserClientConfig = oAuth2AuthorizedClientService.loadAuthorizedClient(
                authorizedClientRegistrationId,
                currentUser.name)
  println("AccessToken: ${currentUserClientConfig.accessToken.tokenValue}")
  println("RefreshToken: ${currentUserClientConfig.refreshToken.tokenValue}")

```

## But Access Tokens can expire

When access tokens expire, the resource server like [like tasks API of google](https://developers.google.com/tasks/v1/reference/tasks/list) will return 401 HTTP status, the simplest solution is to throw an `OAuth2AuthorizationException` which is a type of `AuthenticationException` that will trigger the login flow again. 

But we can also use Refresh Tokens to automatically refresh our tokens, by customizing `RestTemplate` with a request interceptor that will refresh the tokens on expiry

```kotlin
class BearerTokenInterceptor(private val oAuth2AuthorizedClientService: OAuth2AuthorizedClientService) : ClientHttpRequestInterceptor {

    companion object {
        val log: Logger = LoggerFactory.getLogger(BearerTokenInterceptor::class.java)
    }

    private var accessTokenExpiresSkew = Duration.ofMinutes(1)
    private val clock = Clock.systemUTC()

    override fun intercept(request: HttpRequest, body: ByteArray, execution: ClientHttpRequestExecution): ClientHttpResponse {
        val currentUser = SecurityContextHolder.getContext().authentication as OAuth2AuthenticationToken
        val currentUserClientConfig = currentUser.clientConfig()

        if (isExpired(accessToken = currentUserClientConfig.accessToken)) {
            log.info("AccessToken expired, refreshing automatically")
            refreshToken(currentUserClientConfig, currentUser)
        }

        request.headers[AUTHORIZATION] = "Bearer ${currentUserClientConfig.accessToken.tokenValue}"

        return execution.execute(request, body)
    }

    private fun OAuth2AuthenticationToken.clientConfig(): OAuth2AuthorizedClient {
        return oAuth2AuthorizedClientService.loadAuthorizedClient(
                authorizedClientRegistrationId,
                name) ?: throw CredentialsExpiredException("could not load client config for $name, reauthenticate")
    }

    private fun refreshToken(currentClient: OAuth2AuthorizedClient, currentUser: OAuth2AuthenticationToken) {
        val atr = refreshTokenClient(currentClient)
        if (atr == null || atr.accessToken == null) {
            log.info("Failed to refresh token for ${currentUser.name}")
            return
        }

        val refreshToken = atr.refreshToken ?: currentClient.refreshToken
        val updatedClient = OAuth2AuthorizedClient(
                currentClient.clientRegistration,
                currentClient.principalName,
                atr.accessToken,
                refreshToken
        )

        oAuth2AuthorizedClientService.saveAuthorizedClient(updatedClient, currentUser)
    }

    private fun refreshTokenClient(currentClient: OAuth2AuthorizedClient): OAuth2AccessTokenResponse? {

        val formParameters = LinkedMultiValueMap<String, String>()
        formParameters.add(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.value)
        formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN, currentClient.refreshToken?.tokenValue)
        formParameters.add(OAuth2ParameterNames.REDIRECT_URI, currentClient.clientRegistration.redirectUriTemplate)


        val requestEntity = RequestEntity
                .post(URI.create(currentClient.clientRegistration.providerDetails.tokenUri))
                .header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE)
                .body(formParameters)

        return try {
            val r = restTemplate(currentClient.clientRegistration.clientId, currentClient.clientRegistration.clientSecret)
            val responseEntity = r.exchange(requestEntity, OAuth2AccessTokenResponse::class.java)
            responseEntity.body
        } catch (e: OAuth2AuthorizationException) {
            log.error("Unable to refresh token ${e.error.errorCode}")
            throw OAuth2AuthenticationException(e.error, e)
        }
    }

    private fun isExpired(accessToken: OAuth2AccessToken): Boolean {
        val now = this.clock.instant()
        val expiresAt = accessToken.expiresAt ?: return false
        return now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew))
    }

    private fun restTemplate(clientId: String, clientSecret: String): RestTemplate {
        return RestTemplateBuilder()
                .additionalMessageConverters(
                        FormHttpMessageConverter(),
                        OAuth2AccessTokenResponseHttpMessageConverter())
                .errorHandler(OAuth2ErrorResponseErrorHandler())
                .basicAuthentication(clientId, clientSecret)
                .build()
    }

}
```

 > So far I have not found that the oauth2-client can automatically refresh tokens within the user session, Let me know if this is the case :)


# Conclusion

I tried to put together all pieces involved, Please give me feedback if I missed something :)