Custom JWT-based authentication with access token + httpOnly refresh cookie rotation.
Create a new account.
Body:
{
"email": "user@example.com",
"username": "player1",
"password": "minimum8chars"
}Validation:
- Email must be unique
- Username must be unique
- Password must be at least 8 characters
- Registration must be open (checked against DB settings)
- User limit must not be reached
Response (200):
{
"accessToken": "eyJ...",
"user": {
"id": "clx...",
"email": "user@example.com",
"username": "player1",
"rating": 1200,
"role": "USER"
}
}Also sets refresh_token httpOnly cookie.
Errors: 400 (validation), 403 (registration closed/limit), 409 (duplicate)
Body:
{
"email": "user@example.com",
"password": "minimum8chars"
}Checks: credentials, active status, email verification (if enabled)
Response: Same as register.
Errors: 401 (invalid credentials), 403 (deactivated/unverified)
Uses the refresh_token httpOnly cookie. No body required.
- Validates token against DB (SHA-256 hashed)
- Deletes old token, creates new one (rotation)
- Returns new access token + sets new cookie
Response (200):
{ "accessToken": "eyJ..." }Errors: 401 (missing/invalid/expired token)
Deletes refresh token from DB and clears cookie.
Response (200):
{ "success": true }Returns current user profile including preferences.
Response (200):
{
"user": {
"id": "clx...",
"email": "user@example.com",
"username": "player1",
"rating": 1200,
"avatarUrl": null,
"role": "USER",
"darkMode": true,
"boardTheme": "classic",
"pieceSet": "classic",
"createdAt": "2026-01-01T00:00:00.000Z"
}
}Update theme preferences. Saved to DB.
Body (all optional):
{
"darkMode": false,
"boardTheme": "green",
"pieceSet": "modern"
}Valid board themes: classic, wood, green, blue, purple, dark
Valid piece sets: classic, modern, minimal
| Token | Type | Lifetime | Storage |
|---|---|---|---|
| Access | JWT (HS256) | 15 minutes | Memory (Zustand) |
| Refresh | Random 40-byte hex | 7 days | httpOnly cookie (browser), SHA-256 hash (DB) |
- Refresh tokens are rotated on each use (old deleted, new created)
- Access tokens include:
userId,email,username,role - Cookies:
httpOnly,secure(production),sameSite: lax,path: /
The Axios interceptor in lib/api.ts:
- Catches any 401 response
- Calls
/api/v1/auth/refresh - Retries the original request with the new token
- Queues concurrent requests during refresh (no thundering herd)
- If refresh fails → clears state → redirects to
/login