Skip to content
Last updated

Refresh Token Flow

Access tokens expire after 1 hour. Use the refresh token to obtain new access tokens without requiring user re-authentication.

Why Refresh Tokens?

Short-lived access tokens limit the impact of token compromise. Refresh tokens enable:

  • Seamless user experience without repeated logins
  • Enhanced security through token rotation
  • Controlled access revocation

Implementation

Request New Access Token

When your access token expires (HTTP 401 responses), use the refresh token:

POST https://api.askara.ai/oauth2/token
Content-Type: application/json

{
  "grant_type": "refresh_token",
  "refresh_token": "REFRESH_TOKEN",
  "client_id": "CLIENT_ID",
  "client_secret": "CLIENT_SECRET"
}

Response:

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "def502003f8a8c7..."
}

Important: The response includes a new refresh token. Update your stored refresh token immediately as the old one is invalidated.

Token Rotation

Each refresh token is single-use. This rotation mechanism:

  • Detects token theft (reuse of old refresh token)
  • Limits the window of vulnerability
  • Enables security monitoring

Rotation Flow

┌─────────────────────┐
│ Old Refresh Token   │
└──────────┬──────────┘


┌─────────────────────┐
│ POST /oauth2/token  │
└──────────┬──────────┘

           ├──────────────────────┐
           │                      │
           ▼                      ▼
┌──────────────────┐   ┌──────────────────┐
│ New Access Token │   │ New Refresh Token│
└────────┬─────────┘   └────────┬─────────┘
         │                      │
         └──────────┬───────────┘


         ┌──────────────────┐
         │ Update Database  │
         └────────┬─────────┘


      ┌─────────────────────────┐
      │ Old Token Invalidated   │
      └─────────────────────────┘

Error Handling

Invalid Refresh Token

{
  "error": "invalid_grant",
  "error_description": "The refresh token is invalid or expired"
}

Causes:

  • Token already used (rotation)
  • Token revoked by user
  • Token expired (rare, typically long-lived)
  • Invalid client credentials

Action: Redirect user to re-authenticate via Authorization Code Flow.

Revoked Access

If a user revokes your application's access, all tokens become invalid. Implement proper error handling to detect this scenario.

Security Best Practices

Storage

  • Encrypt refresh tokens in your database
  • Never expose refresh tokens to client-side code
  • Use separate encryption keys from other sensitive data

Rotation Implementation

function refreshAccessToken($oldRefreshToken) {
    $response = $httpClient->post('https://api.askara.ai/oauth2/token', [
        'json' => [
            'grant_type' => 'refresh_token',
            'refresh_token' => $oldRefreshToken,
            'client_id' => getenv('ASKARA_CLIENT_ID'),
            'client_secret' => getenv('ASKARA_CLIENT_SECRET')
        ]
    ]);

    $tokens = json_decode($response->getBody(), true);

    // Atomic update: only commit if both tokens are updated
    $db->beginTransaction();
    try {
        $db->update('oauth_tokens', [
            'access_token' => $tokens['access_token'],
            'refresh_token' => $tokens['refresh_token'],
            'expires_at' => time() + $tokens['expires_in']
        ]);
        $db->commit();
    } catch (Exception $e) {
        $db->rollback();
        throw $e;
    }

    return $tokens['access_token'];
}

Expiration Handling

Check token expiration before making API requests to avoid unnecessary failures:

function getValidAccessToken() {
    $tokenData = $db->getTokenData();

    // Refresh if token expires within 5 minutes
    if ($tokenData['expires_at'] < time() + 300) {
        return refreshAccessToken($tokenData['refresh_token']);
    }

    return $tokenData['access_token'];
}

Automatic Retry Pattern

Implement automatic token refresh on 401 responses:

function makeApiRequest($url, $data) {
    $accessToken = getValidAccessToken();

    try {
        return $httpClient->post($url, [
            'headers' => ['Authorization' => 'Bearer ' . $accessToken],
            'json' => $data
        ]);
    } catch (UnauthorizedException $e) {
        // Token expired during request, refresh and retry once
        $accessToken = refreshAccessToken($db->getRefreshToken());
        return $httpClient->post($url, [
            'headers' => ['Authorization' => 'Bearer ' . $accessToken],
            'json' => $data
        ]);
    }
}

Monitoring

Track refresh token usage to detect anomalies:

  • Unusually frequent refreshes (potential token theft)
  • Failed refresh attempts (compromised credentials)
  • Geographic anomalies (access from unexpected locations)

Next Steps