Refresh Token
Method
REST method is adopted.
HTTP Method
POST: Token refresh
Naming Convention
To unify naming of query parameters and nodes and improve readability, snake_case is used for URIs and nodes in JSON during requests.
Request and Response
Headers
Meta information is set in HTTP headers rather than in the response body.
Request Headers
Content-Type:application/json
Response Headers
Content-Type:application/json
Refresh Token
URI
Request Body
Request body is JSON.
When the admin Cognito app client has a client secret (COGNITO_ADMIN_CLIENT_SECRET), Cognito requires a correct SECRET_HASH, which is derived from a username string. In practice, clients should send cognito_sub from the login (or /me) response (JWT sub); email alone may be rejected depending on user pool configuration (e.g. email alias sign-in). If both are sent, cognito_sub is preferred for computing the hash.
{
"refresh_token": "eyJjdGkiOiI0YjM1ZjQ2NS0yM2E0LTRiZTAtYjM2Mi01...",
"email": "admin@example.com",
"cognito_sub": "1111-aaaa-2222-bbbb-3333-cccc4444dddd"
}
Validation Rules
| Field | Rule |
|---|---|
| refresh_token | Required. Must be a non-empty string. |
Optional. Valid email format. With a client secret, provide this and/or cognito_sub (at least one required in that case). |
|
| cognito_sub | Optional. Non-empty string. User sub from login / JWT. Recommended when using a confidential client (client secret). |
Response (200 OK)
Response is JSON.
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJjdGkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ...",
"user": {
"id": 1,
"name": "Admin User",
"cognito_sub": "1111-aaaa-2222-bbbb",
"email": "admin@example.com",
"role": {
"id": 1,
"name": "admin"
}
}
}
refresh_token may be omitted if Cognito does not return a new refresh token for the session.
Authentication
This endpoint does not require authentication. The refresh token is provided in the request body instead of the Authorization header.
Exception Handling
Exception handling status codes are as follows.
| Description | Status Code | Status Name |
|---|---|---|
| Invalid request body or validation error | 400 | Bad Request |
| Invalid or expired refresh token | 401 | Unauthorized |
Missing email and cognito_sub when the app client has a client secret |
401 | Unauthorized |
Invalid ID token claims after refresh (e.g. missing email or sub) |
401 | Unauthorized |
| Admin not found | 401 | Unauthorized |
| Internal server error | 500 | Internal Server Error |
Process Flow
Sequence Diagram
sequenceDiagram
participant Client
participant API as Hono Router
participant Validate as Zod Validation
participant Service as Auth Service
participant Cognito as AWS Cognito
participant DB as PostgreSQL Database
Client->>API: POST /api/v1/auth/refresh_token
API->>Validate: Validate body (refresh_token, optional email, cognito_sub)
Validate-->>API: Validation passed
API->>Service: refreshToken(refresh_token, email, cognito_sub, ip, user_agent)
Service->>Cognito: initiateAuth(REFRESH_TOKEN_AUTH, SECRET_HASH if client secret)
alt Token Refresh Success
Cognito-->>Service: New Access Token + ID Token
Service->>Service: Verify ID token, extract sub and email
Service->>DB: Find admin by cognito_sub
DB-->>Service: Admin record
Service->>DB: Create session (hashed token, ip, user_agent, expires_at)
DB-->>Service: Session created
Service-->>API: { token, refresh_token?, user }
API-->>Client: 200 OK { token, refresh_token?, user }
else Token Refresh Failed
Cognito-->>Service: NotAuthorizedException
Service-->>API: throw UnauthorizedError
API-->>Client: 401 Unauthorized
end
Routes Layer
API routing is performed here. Validates request body using Zod schema, extracts client IP and user agent, then delegates to the auth service.
Source: src/routes/v1/auth.ts
Services Layer
This section describes business logic. It resolves the username string for SECRET_HASH when a client secret is configured (cognito_sub preferred, else normalized email), calls Cognito REFRESH_TOKEN_AUTH, verifies the new ID token, extracts admin identity, creates a new session record, and returns the new access token, optional new refresh token, and user info.
Source: src/services/auth.ts
Repositories Layer
This section describes access to databases and external services. It handles Cognito refresh via InitiateAuthCommand with REFRESH_TOKEN_AUTH, computes SECRET_HASH from the caller-supplied username string (not the refresh token value), performs admin lookups by Cognito sub, and session creation with SHA-256 access-token hashing.
Source: src/repositories/
Security
- Refresh tokens are single-use โ a new refresh token is issued with each refresh
- Confidential clients must send
cognito_sub(or email, if valid for your pool) soSECRET_HASHmatches Cognito; persistcognito_subfrom login next to the refresh token - Sign-in email is normalized (trim + lowercase) for password and password-reset flows so
USERNAME/SECRET_HASHstay consistent - Client IP address and user agent are logged for audit purposes
- Session tokens are stored as SHA-256 hashes in the database
- Failed refresh attempts are logged with request context
- Expired or revoked refresh tokens are rejected by Cognito