Access tokens expire after 1 hour. Use the refresh token to obtain new access tokens without requiring user re-authentication.
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
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.
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
┌─────────────────────┐
│ Old Refresh Token │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ POST /oauth2/token │
└──────────┬──────────┘
│
├──────────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ New Access Token │ │ New Refresh Token│
└────────┬─────────┘ └────────┬─────────┘
│ │
└──────────┬───────────┘
│
▼
┌──────────────────┐
│ Update Database │
└────────┬─────────┘
│
▼
┌─────────────────────────┐
│ Old Token Invalidated │
└─────────────────────────┘{
"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.
If a user revokes your application's access, all tokens become invalid. Implement proper error handling to detect this scenario.
- Encrypt refresh tokens in your database
- Never expose refresh tokens to client-side code
- Use separate encryption keys from other sensitive data
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'];
}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'];
}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
]);
}
}Track refresh token usage to detect anomalies:
- Unusually frequent refreshes (potential token theft)
- Failed refresh attempts (compromised credentials)
- Geographic anomalies (access from unexpected locations)
- Authorization Code Flow - Initial authentication
- API Reference - API endpoints
- OAuth2 Overview - All authentication flows