springspring-bootkotlinspring-securityoauth-2.0

Getting refresh token for gmail API with Spring Security


I have an app that connects to the Gmail API of its users. It authenticates by using the refreshToken to get an accessToken.

Libraries in use: Spring boot 3.4.1 -> Spring security 6.4.2

The refresh token is stored in the database and can be used for a long time, but is invalidated if password is changed for example. We need a way to get a new refresh token easily from an integration page, but cannot see how this can be done programatically with Spring security in a simple way.

Right now I have manually found the refreshToken by doing the following:

  1. Added .oauth2Login(Customizer.withDefaults()) to the SecurityFilterChain
  2. Logged in with the Google user, and set a breakpoint in
class: DefaultAuthorizationCodeTokenResponseClient
method: public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
  1. Copy the refreshToken from the variables in the debugger

In my properties file I have:

spring.security.oauth2.client.registration.google.client-id={....}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=https://mail.google.com/,https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline

So the question is simply Is there any Spring security features that can simplify this process of getting the refreshToken?

Ideal flow:

  1. User clicks "integrate Gmail" link
  2. Taken to Oauth 2.0 login
  3. redirects back to app after login and in the backend refreshToken has been saved to DB
private fun refreshAccessToken(refreshToken: String): Credential {
        val credential = GoogleCredential.Builder().setTransport(httpTransport)
            .setJsonFactory(JSON_FACTORY)
            .setClientSecrets(CLIENT_ID, CLIENT_SECRET)
            .build()
        credential.refreshToken = refreshToken
        credential.refreshToken()
        return credential
    }

Solution

    1. Set up in Google Cloud Console:

      • Go to the Google Cloud Console
      • Select an existing project
      • Add your authorized redirect URI in web client credential like (http://localhost:8080/auth/google/callback)
    2. When a user needs to authenticate:

      • Redirect them to the authUrl generated in the auth URL
    private fun buildOAuthUrl(): String {
        val encodedRedirectUri = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.toString())
        val encodedScope = scope.split(" ")
            .map { URLEncoder.encode(it.trim(), StandardCharsets.UTF_8.toString()) }
            .joinToString(" ")
        return UriComponentsBuilder.fromUriString(oauthUrl)
            .queryParam("client_id", clientId)
            .queryParam("redirect_uri", encodedRedirectUri)
            .queryParam("response_type", "code")
            .queryParam("scope", encodedScope)
            .queryParam("access_type", "offline")
            .queryParam("prompt", "consent")
            .build()
            .toUriString()
    }
    
    1. After getting auth code:

      • We are calling GoogleAuthorizationCodeTokenRequest() to generate refresh token
    val response = GoogleAuthorizationCodeTokenRequest(
                httpTransport,
                jsonFactory,
                clientId,
                clientSecret,
                authCode(We will get from the callback URL),
                redirectUri
            ).setGrantType("authorization_code").execute()
    
        val refreshToken = response.refreshToken