Refresh Tokens
Overview
Section titled “Overview”Refresh tokens are long-lived credentials used to obtain new access tokens without re-authenticating. They are:
- 256-bit random (64 hex characters)
- Stored in D1 (server-side, not in the token itself)
- Rotated on every use (old token revoked, new one issued)
- Scoped to a project (cannot be used across projects)
Refresh Grant
Section titled “Refresh Grant”POST https://auth.beshoy.ai/oauth/tokenContent-Type: application/jsonRequest Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | "refresh_token" |
refresh_token | string | Yes | Current refresh token |
client_id | string | Yes | Project ID |
client_secret | string | Yes | Project client secret |
Example Request
Section titled “Example Request”curl -X POST https://auth.beshoy.ai/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "refresh_token", "refresh_token": "a1b2c3d4e5f6...", "client_id": "proj_gym", "client_secret": "your-client-secret-hex" }'Success Response
Section titled “Success Response”{ "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "f6e5d4c3b2a1...", "token_type": "Bearer", "expires_in": 300}Token Rotation
Section titled “Token Rotation”Every refresh operation follows this sequence:
- Look up the refresh token in D1
- Verify it belongs to the requesting
client_id - Check revocation status (with grace period)
- Check expiration
- Revoke the old token immediately
- Verify user status (blocked check) or PIN status (revoked check)
- Issue new access token + new refresh token
- Return both
This means a stolen refresh token can only be used once. After that, the legitimate client will fail to refresh (their token was already consumed), signaling a potential breach.
Grace Period
Section titled “Grace Period”When a token is revoked, there’s a 60-second grace period where it’s still accepted. This handles a common race condition:
Request A: uses refresh_token_1 → rotates to refresh_token_2Request B: uses refresh_token_1 (sent before A's response arrived)Without the grace period, Request B would fail. With it, both succeed within the 60-second window.
After 60 seconds, the revoked token is permanently rejected.
Blocked User Handling
Section titled “Blocked User Handling”During refresh, the auth service checks the user’s status in project_users:
status = "active"→ refresh succeedsstatus = "blocked"→ returns403 Account blocked
This means blocking a user takes effect within the access token’s lifetime (5 minutes max).
PIN Session Refresh
Section titled “PIN Session Refresh”For PIN-based sessions (is_pin_session = 1):
- The PIN’s status is checked in
project_pins - If
status = "revoked"→ returns403 PIN revoked - Privileges are re-read from the PIN record (reflects any updates)
Expiration
Section titled “Expiration”| Session type | Refresh token lifetime |
|---|---|
| User session | 7 days |
| PIN session | 30 days |
Expired tokens are rejected with 401 Refresh token expired.
Error Responses
Section titled “Error Responses”| Error | Status | Cause |
|---|---|---|
Missing refresh_token | 400 | No token in request |
Invalid refresh token | 401 | Token not found, wrong project, or revoked past grace period |
Refresh token expired | 401 | Token past its expiration date |
Account blocked | 403 | User’s project status is “blocked” |
PIN revoked | 403 | PIN has been revoked by admin |