Skip to content

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

/api/v1/auth/refresh_token

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.
email 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) so SECRET_HASH matches Cognito; persist cognito_sub from login next to the refresh token
  • Sign-in email is normalized (trim + lowercase) for password and password-reset flows so USERNAME / SECRET_HASH stay 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