Learnitweb

Refresh Token Grant Type in OAuth 2.0 (with Keycloak)

What is the Refresh Token Grant Type?

The Refresh Token Grant Type is a mechanism that allows a client (application) to obtain a new access token using a refresh token without requiring the user to log in again.

Why is It Needed?

  • Access tokens are short-lived (e.g., 5–15 minutes) for security reasons.
  • Instead of forcing the user to log in again when the access token expires, we can use the refresh token to get a new access token.
  • This allows for a smooth user experience and persistent sessions.

Components

TermDescription
Authorization ServerThe identity provider that issues tokens (e.g., Keycloak)
ClientThe application (e.g., React app, Spring Boot app)
Access TokenToken used to access protected resources
Refresh TokenToken used to obtain a new access token after expiry
Resource ServerBackend APIs that require a valid access token

How Does the Refresh Token Grant Type Work?

1. Initial Login

The user logs in using username/password (or via authorization code).
Keycloak returns:

{
  "access_token": "eyJhbGciOi...",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOi...",
  "refresh_expires_in": 1800
}
  • access_token lasts for ~5 minutes
  • refresh_token lasts for ~30 minutes or longer

2. When Access Token Expires

The client sends a POST request to Keycloak to get a new access token using the refresh token.

Refresh Token Request Format

URL:

POST /realms/<realm-name>/protocol/openid-connect/token
Host: http://localhost:8080

Headers:

Content-Type: application/x-www-form-urlencoded

Body:

grant_type=refresh_token
client_id=myclient
client_secret=client-secret   (if confidential client)
refresh_token=<refresh_token>

Refresh Token Example with curl

curl -X POST http://localhost:8080/realms/myrealm/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=myclient" \
  -d "client_secret=mysecret" \
  -d "refresh_token=eyJhbGciOi..."

Server Response:

{
  "access_token": "eyJhbGciOi...",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOi... (new)",
  "refresh_expires_in": 1800,
  "token_type": "Bearer"
}
  • The new access_token is valid again.
  • Keycloak rotates the refresh token: you get a new one and should discard the old one.

Implementing Refresh Token Flow in Spring Boot

Spring Boot doesn’t automatically handle refresh tokens, but you can call the token endpoint manually like this:

public Map<String, Object> refreshAccessToken(String refreshToken) {
    RestTemplate restTemplate = new RestTemplate();

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type", "refresh_token");
    body.add("client_id", "myclient");
    body.add("client_secret", "<client-secret>");
    body.add("refresh_token", refreshToken);

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);

    ResponseEntity<Map> response = restTemplate.postForEntity(
        "http://localhost:8080/realms/myrealm/protocol/openid-connect/token",
        request,
        Map.class
    );

    return response.getBody(); // contains new access and refresh tokens
}

React Apps (Using Keycloak JS Adapter)

When using keycloak-js:

keycloak.updateToken(30).then(refreshed => {
  if (refreshed) {
    console.log("Token refreshed successfully");
  } else {
    console.log("Token is still valid");
  }
}).catch(() => {
  console.error("Token refresh failed");
});
  • The library automatically uses the refresh token to get a new access token when needed.
  • This is the recommended way to use refresh tokens in browser-based apps.

Refresh Token Errors

ErrorCause
invalid_grantRefresh token is invalid or expired
unauthorized_clientClient is not allowed to use the grant type
invalid_clientWrong client credentials
401 UnauthorizedRefresh token expired, user must re-authenticate

Security Best Practices

  • Never expose the refresh token to the browser (unless using a secure JS library like keycloak-js).
  • Use HttpOnly cookies or keep it in backend memory.
  • Rotate refresh tokens if supported (Keycloak does this).
  • Set appropriate refresh token TTL in Keycloak for session control.
  • Use HTTPS to prevent token interception.