Refresh Tokens
Production - POST /api/v1/auth/refresh
Refresh tokens enable long-lived sessions without requiring users to re-enter credentials. The IAM service implements refresh token rotation with token family tracking to detect and prevent token reuse attacks.
6.2.12Token Refresh Flow
Client AuthController AuthenticationService
| | |
|--- POST /auth/refresh --->| |
| { refreshToken: "..." } |--- refreshToken() ----->|
| | |--- Find valid token
| | |--- Check user status
| | |--- Generate new access token
| | |--- Rotate refresh token
| | |--- Save new token in family
|<-- 200 (AuthResponse) ----|<-- AuthResponse ---------|Request
curl -X POST http://localhost:8081/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}'Request Schema
| Field | Type | Required | Description |
|---|---|---|---|
refreshToken | String | Yes | The refresh token to exchange |
Response (200 OK)
{
"accessToken": "eyJhbGciOiJIUzI1NiJ9...<new-access-token>",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9...<new-refresh-token>",
"tokenType": "Bearer",
"expiresIn": 900,
"user": {
"id": 1,
"email": "jane.smith@example.com",
"firstName": "Jane",
"lastName": "Smith",
"displayName": "Jane Smith",
"tenantId": "00000000-0000-0000-0000-000000000001",
"roles": ["ROLE_USER"],
"emailVerified": true
}
}6.2.13Token Rotation
Every refresh token exchange produces a new refresh token and invalidates the old one. This is called refresh token rotation.
How Token Families Work
Each refresh token belongs to a token family identified by a UUID. When a user logs in, a new family is created. Each rotation creates a new token in the same family:
Login: RefreshToken_A (family: F1, active)
Refresh 1: RefreshToken_A (family: F1, rotated) -> RefreshToken_B (family: F1, active)
Refresh 2: RefreshToken_B (family: F1, rotated) -> RefreshToken_C (family: F1, active)Reuse Detection
If a previously rotated token is used again, it indicates a potential token theft:
RefreshToken refreshToken = refreshTokenRepository.findValidToken(token, Instant.now())
.orElseThrow(() -> {
// Check if token exists but is revoked (potential reuse attack)
refreshTokenRepository.findByToken(token).ifPresent(t -> {
if (t.isRevoked()) {
log.warn("Refresh token reuse detected for family: {}", t.getTokenFamily());
// Revoke ALL tokens in the family
refreshTokenRepository.revokeAllByTokenFamily(
t.getTokenFamily(), Instant.now(), "Token reuse detected"
);
}
});
return new TokenException("Invalid or expired refresh token");
});When reuse is detected:
- All tokens in the family are revoked
- The user must re-authenticate with credentials
- The event is logged for security audit
User Status Checks
Before issuing new tokens, the service verifies the user account is still valid:
User user = refreshToken.getUser();
if (!user.isEnabled() || user.isAccountLocked()) {
refreshToken.revoke("User account disabled or locked");
refreshTokenRepository.save(refreshToken);
throw new AuthenticationException("User account is not available");
}6.2.14Token Expiration
| Token Type | Default Expiration | Configurable |
|---|---|---|
| Access Token | 15 minutes | security.jwt.access-token-expiration |
| Refresh Token | 7 days | security.jwt.refresh-token-expiration |
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
TOKEN_INVALID | 401 | Refresh token not found or already revoked |
TOKEN_EXPIRED | 401 | Refresh token has expired |
TOKEN_REUSE_DETECTED | 401 | Previously used token detected (all family tokens revoked) |
AUTHENTICATION_FAILED | 401 | User account disabled or locked |