Files
2026-02-17 09:29:34 -06:00

66 lines
1.5 KiB
PHP

<?php
// phpcs:ignoreFile
/**
* POST /api/auth/refresh
*
* Exchange a valid refresh token for a new access token.
*/
declare(strict_types=1);
$body = api_get_json_body();
api_require_fields($body, ['refresh_token']);
$refreshToken = trim((string) $body['refresh_token']);
// Decode the refresh token
$payload = jwt_decode_token($refreshToken);
if ($payload === null) {
api_error('Invalid or expired refresh token', 401);
}
if (!isset($payload['type']) || $payload['type'] !== 'refresh') {
api_error('Invalid token type', 401);
}
$username = $payload['sub'] ?? null;
$tokenId = $payload['jti'] ?? null;
if ($username === null || $tokenId === null) {
api_error('Invalid refresh token payload', 401);
}
// Verify token exists in database and is not revoked
if (!jwt_validate_refresh_token($username, $tokenId)) {
api_error('Refresh token has been revoked or does not exist', 401);
}
// Look up current user data
$member = auth_find_member($username);
if ($member === null) {
api_error('User no longer exists', 401);
}
// Revoke the old refresh token (rotation for security)
jwt_revoke_refresh_token($tokenId);
// Generate new tokens
$accessToken = jwt_create_access_token($member);
$newRefreshData = jwt_create_refresh_token($member);
// Store new refresh token
jwt_store_refresh_token(
$member['username'],
$newRefreshData['token_id'],
$newRefreshData['expires_at']
);
api_success([
'access_token' => $accessToken,
'refresh_token' => $newRefreshData['token'],
'token_type' => 'Bearer',
'expires_in' => 900,
]);