How OAuth2 Token Validation, Generation, and Refresh Really Work

This article explains the complete OAuth2 token lifecycle—including how resource servers validate incoming tokens, how the authorization server creates and reuses access tokens, and the mechanisms for passive and active token refresh—complete with Java code examples and practical client‑side strategies.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
How OAuth2 Token Validation, Generation, and Refresh Really Work

Token Validation Logic

// CheckTokenEndpoint.checkToken
@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
    // Retrieve full token information from tokenStore
    OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
    if (token == null) {
        throw new InvalidTokenException("Token was not recognised");
    }
    if (token.isExpired()) {
        throw new InvalidTokenException("Token has expired");
    }
    // Load authentication and authorities associated with the token
    OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
    return accessTokenConverter.convertAccessToken(token, authentication);
}

When a client sends a token in the request header, the resource server intercepts it, forwards the token to the authorization server’s check_token endpoint, and returns an error if the token is invalid or expired.

Token Generation Logic

// DefaultTokenServices.createAccessToken logic
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    // Look for an existing token for the user
    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null) {
        // If the existing token is expired, remove it (and its refresh token if present)
        if (existingAccessToken.isExpired()) {
            if (existingAccessToken.getRefreshToken() != null) {
                refreshToken = existingAccessToken.getRefreshToken();
                tokenStore.removeRefreshToken(refreshToken);
            }
            tokenStore.removeAccessToken(existingAccessToken);
        } else {
            // Return the still‑valid token and store the user‑token relationship
            tokenStore.storeAccessToken(existingAccessToken, authentication);
            return existingAccessToken;
        }
    }
    // No valid token exists – create a new one
    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    // Store refresh token if one was generated
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
        tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken;
}

If a user already has a token, the service returns it instead of creating a new one; otherwise a fresh token (and optional refresh token) is generated and persisted.

Token Refresh Logic

curl --location --request POST 'http://auth-server/oauth/token?grant_type=refresh_token' \
--header 'Authorization: Basic dGVzdDp0ZXN0' \
--header 'VERSION: dev' \
--data-urlencode 'scope=server' \
--data-urlencode 'refresh_token=eccda61e-0c68-43af-8f67-6302cb389612'

When a valid, unused refresh_token is presented, the authorization server invokes the RefreshTokenGranter to issue a new access token.

public class RefreshTokenGranter extends AbstractTokenGranter {
    @Override
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
        return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
    }
}
@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
        throws AuthenticationException {
    createRefreshedAuthentication(authentication, tokenRequest);
    if (!reuseRefreshToken) {
        tokenStore.removeRefreshToken(refreshToken);
        refreshToken = createRefreshToken(authentication);
    }
    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    if (!reuseRefreshToken) {
        tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
    }
    return accessToken;
}

When Should the Client (Frontend) Refresh?

Passive Refresh

Passive refresh flow
Passive refresh flow

The client sends a request with an access token to a protected resource.

The resource server detects the token and forwards it to the auth server’s check_token endpoint.

If the token is expired, the auth server returns an error.

The client receives the error, then calls the auth server with its refresh_token.

The auth server issues a new access token, and the client retries the original request.

The drawback is that the user experiences a failed request before the token is refreshed.

Active Refresh

Active refresh flow
Active refresh flow

The client periodically calculates the remaining lifetime of its access token.

Before the token expires, it proactively requests a new token using the stored refresh_token.

Active refresh avoids request failures but consumes client resources to monitor token expiry.

// Example of a periodic token‑expiry check in a Vue component
refreshToken() {
  this.refreshTime = setInterval(() => {
    const token = getStore({ name: 'access_token', debug: true })
    if (this.validatenull(token)) { return }
    if (this.expires_in <= 1000 && !this.refreshLock) {
      this.refreshLock = true
      this.$store.dispatch('RefreshToken')
        .catch(() => { clearInterval(this.refreshTime) })
      this.refreshLock = false
    }
    this.$store.commit('SET_EXPIRES_IN', this.expires_in - 10)
  }, 10000)
},
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendSecurityAuthenticationOAuth2Token
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.