Information Security 5 min read

How to Ensure Consistent OAuth2 Tokens Across Multiple Clients with Custom Key Generation

This article explains how to achieve consistent OAuth2 token handling across multiple clients and tenants by customizing the token key generation, overriding DefaultTokenServices logic, and configuring a RedisTokenStore with a custom AuthenticationKeyGenerator in a Spring Boot application.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
How to Ensure Consistent OAuth2 Tokens Across Multiple Clients with Custom Key Generation

Purpose

Explain how to keep token state consistent across different clients and tenants, ensuring that logout on one client invalidates tokens on all others.

Default DefaultTokenServices creation logic

<code>@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    // 1. Check if token exists
    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null) {
        if (existingAccessToken.isExpired()) {
            if (existingAccessToken.getRefreshToken() != null) {
                refreshToken = existingAccessToken.getRefreshToken();
                tokenStore.removeRefreshToken(refreshToken);
            }
            tokenStore.removeAccessToken(existingAccessToken);
        } else {
            tokenStore.storeAccessToken(existingAccessToken, authentication);
            return existingAccessToken;
        }
    }
    // 2. Create new token
    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    // In case it was modified
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
        tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken;
}
</code>

Determine if current user has a token

Shows the default logic of

RedisTokenStore

and the key generation.

<code>OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
    // build default key
    String key = authenticationKeyGenerator.extractKey(authentication);
    // add prefix
    byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
    RedisConnection conn = getConnection();
    try {
        byte[] bytes = conn.get(serializedKey);
        OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
        if (accessToken != null) {
            OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
            if (storedAuthentication == null ||
                !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication))) {
                storeAccessToken(accessToken, authentication);
            }
        }
        return accessToken;
    } finally {
        conn.close();
    }
}
</code>

DefaultAuthenticationKeyGenerator key composition

<code>public String extractKey(OAuth2Authentication authentication) {
    Map<String, String> values = new LinkedHashMap<>();
    OAuth2Request authorizationRequest = authentication.getOAuth2Request();
    if (!authentication.isClientOnly()) {
        values.put(USERNAME, authentication.getName());
    }
    values.put(CLIENT_ID, authorizationRequest.getClientId());
    if (authorizationRequest.getScope() != null) {
        values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<>(authorizationRequest.getScope())));
    }
    return generateKey(values);
}
</code>

Rewrite token key generation rules

<code>public class PigxAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
    private static final String SCOPE = "scope";
    private static final String USERNAME = "username";

    @Override
    public String extractKey(OAuth2Authentication authentication) {
        Map<String, String> values = new LinkedHashMap<>();
        OAuth2Request authorizationRequest = authentication.getOAuth2Request();
        if (!authentication.isClientOnly()) {
            values.put(USERNAME, authentication.getName());
        }
        values.put(CLIENT_ID, authorizationRequest.getClientId());
        if (authorizationRequest.getScope() != null) {
            values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<>(authorizationRequest.getScope())));
        }
        // multi‑tenant handling can be added here
        return generateKey(values);
    }
}
</code>

Inject customized TokenStore

<code>@Bean
public TokenStore tokenStore() {
    RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
    tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX);
    tokenStore.setAuthenticationKeyGenerator(new PigxAuthenticationKeyGenerator());
    return tokenStore;
}
</code>

Summary

Custom authentication key generator ensures token consistency across clients and tenants.

Configure RedisTokenStore with the custom generator and appropriate key prefix.

Refer to the author's blog for more OAuth2 extensions and a sample RBAC project.

JavaRedisOAuth2Token ManagementSpring Security
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

login 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.