This API uses secure opaque tokens for authentication. These tokens are cryptographically secure random strings that are validated against database sessions.
Authorization
header as a Bearer token for all
protected endpoints.Example authorization header:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
The authentication system uses opaque tokens with dual expiration times for enhanced security and user experience:
Token Type | Purpose | Lifetime | Storage |
---|---|---|---|
Access Token | API request authentication | 30 minutes | Client memory/secure storage |
Refresh Token | Obtain new access tokens | 6 months (sliding window) | Secure client storage only |
CSRF Token | Request validation | Same as access token | Client memory |
For persistent authentication, clients should implement automatic token refresh:
/api/auth/refresh
Token Refresh Example:
// Client detects 401 response if (response.status === 401) { const refreshResponse = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Authorization': `Bearer ${currentAccessToken}`, // Required for consistency 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: storedRefreshToken // Takes priority over header token }) }); if (refreshResponse.ok) { const newTokens = await refreshResponse.json(); // Update stored tokens and retry original request updateStoredTokens(newTokens); return retryOriginalRequest(); } else { // Refresh failed, redirect to login redirectToLogin(); } }
For a seamless user experience, implement automatic token refresh in your client application:
Token Manager Class:
class TokenManager { setTokens(authResponse) { this.accessToken = authResponse.token; // API returns 'token' this.refreshToken = authResponse.refreshToken; this.csrfToken = authResponse.csrfToken; this.expiresAt = new Date(Date.now() + authResponse.expiresIn * 1000); // Store in secure storage localStorage.setItem('tokens', JSON.stringify({ accessToken: this.accessToken, refreshToken: this.refreshToken, csrfToken: this.csrfToken, expiresAt: this.expiresAt.toISOString() })); } isAccessTokenExpired() { return !this.expiresAt || new Date() >= this.expiresAt; } }
API Client with Auto-Refresh:
class ApiClient { async makeRequest(url, options = {}) { // Check if token needs refresh if (tokenManager.isAccessTokenExpired()) { await authService.refreshTokens(); } // Add auth headers const headers = { ...options.headers, 'Authorization': `Bearer ${tokenManager.getAccessToken()}`, 'x-csrf-token': tokenManager.getCsrfToken() }; const response = await fetch(url, { ...options, headers }); // Handle 401 with retry if (response.status === 401) { const newToken = await authService.refreshTokens(); if (newToken) { headers.Authorization = `Bearer ${newToken}`; return fetch(url, { ...options, headers }); } } return response; } }
Key Implementation Features:
This API implements CSRF (Cross-Site Request Forgery) protection for all mutation operations (POST, PUT, PATCH, DELETE).
x-csrf-token
header for all subsequent mutation
requests.Example authentication response with CSRF token:
{ "token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...", "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", "csrfToken": "a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3", "expiresIn": 1800, "user": { "id": 1, "username": "John Doe", "email": "john@example.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Doe", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": "2023-01-15T08:35:00Z", "updatedAt": "2023-01-15T08:30:00Z", "subscription": null } }
Example request with CSRF protection:
// Request headers Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3 Content-Type: application/json
/api/auth/google/callback
Handle Google OAuth callback and exchange authorization code for tokens
Request Body:
{ "code": "4/0AfJohXlxxx...", "mode": "login", "redirectUri": "http://localhost:3000/auth/google/callback" }
Field Descriptions:
code
(required) - Authorization code received from Google OAuth flowmode
(required) - Authentication mode, either "login" or "register"redirectUri
(required) - Redirect URI that must match the one configured in Google Cloud ConsoleResponse (200 OK):
{ "googleIdToken": "eyJzdWIiOiIxMjM0NTY3ODkw...", "message": "Google authentication successful" }
Error Responses:
{ "error": "Missing required parameters", "message": "code, mode, and redirectUri are required", "code": "GOOGLE_AUTH_MISSING_PARAMS" } { "error": "Invalid mode", "message": "mode must be 'login' or 'register'", "code": "GOOGLE_AUTH_INVALID_MODE" } { "error": "Failed to exchange authorization code", "message": "Google authentication failed", "code": "GOOGLE_AUTH_TOKEN_EXCHANGE_FAILED" } { "error": "Failed to retrieve user information", "message": "Could not get user profile from Google", "code": "GOOGLE_AUTH_USER_INFO_FAILED" }
Notes:
googleIdToken
can be passed to /api/auth/google/login
or /api/auth/google/register
/api/auth/google/login
Authenticate with Google ID token
Request Body:
{ "googleIdToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6..." }
Response (200 OK):
{ "token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...", // Valid for 30 minutes "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", // Valid for 6 months "csrfToken": "a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3", "expiresIn": 1800, // 30 minutes in seconds "user": { "id": 1, "username": "John Doe", "email": "john@gmail.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Doe", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": "2023-01-15T08:35:00Z", "updatedAt": "2023-01-15T08:30:00Z", "subscription": null } }
Error Responses:
{ "message": "Invalid Google ID token", "code": "AUTH_INVALID_GOOGLE_TOKEN" } { "message": "No account found with this Google email. Please register first.", "code": "AUTH_GOOGLE_USER_NOT_FOUND" }
Notes:
/api/auth/google/register
Register a new user account with Google ID token
Request Body:
{ "googleIdToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6..." }
Response (201 Created):
{ "token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...", // Valid for 30 minutes "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", // Valid for 6 months "csrfToken": "f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3", "expiresIn": 1800, // 30 minutes in seconds "user": { "id": 2, "username": "John Doe", "email": "john@gmail.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Doe", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": "2023-01-15T08:30:00Z", "updatedAt": null, "subscription": null } }
Error Responses:
{ "message": "Invalid Google ID token", "code": "AUTH_INVALID_GOOGLE_TOKEN" } { "message": "Email address is already registered. Please use login instead.", "code": "AUTH_EMAIL_EXISTS" }
Notes:
/api/auth/login
Log in with email and password
Request Body:
{ "email": "user@example.com", "password": "yourpassword" }
Response (200 OK):
{ "token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...", // Valid for 30 minutes "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", // Valid for 6 months "csrfToken": "a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3", "expiresIn": 1800, // 30 minutes in seconds "user": { "id": 1, "username": "John Doe", "email": "john@example.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Doe", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": "2023-01-15T08:35:00Z", "updatedAt": "2023-01-15T08:30:00Z", "subscription": null } }
Error Responses:
{ "message": "Invalid credentials", "code": "AUTH_INVALID_CREDENTIALS" } { "message": "Your email address has not been verified. A verification code has been sent to your email address.", "code": "AUTH_EMAIL_NOT_VERIFIED" }
/api/auth/logout
Log out the current user (invalidate token)
Headers (Optional):
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Note: Authorization header is optional. If provided, the specific session will be invalidated. If not provided, logout will still succeed.
Response (200 OK):
{ "success": true, "message": "Logged out successfully" }
/api/auth/refresh-token
/api/auth/refresh
Refresh authentication token when the current one is about to expire or has expired. Both endpoints are supported for compatibility.
Headers (Optional):
Authorization: Bearer [current_access_token_or_refresh_token]
Request Body (Optional - takes priority if provided):
{ "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn..." }
Notes:
refreshToken
is provided in the request body, it will be used instead of the token from the Authorization header/api/auth/refresh-token
is the legacy endpoint, /api/auth/refresh
is preferredResponse (200 OK):
{ "token": "DEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu...", // New token valid for 30 minutes "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", // New refresh token valid for 6 months "csrfToken": "f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3", // New CSRF token "expiresIn": 1800, // 30 minutes in seconds "user": { "id": 1, "username": "John Doe", "email": "john@example.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Doe", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": "2023-01-15T08:35:00Z", "updatedAt": "2023-01-15T08:30:00Z", "subscription": null } }
Error Responses:
{ "message": "No token provided", "code": "AUTH_NO_TOKEN" } { "message": "Invalid token format", "code": "AUTH_INVALID_TOKEN_FORMAT" } { "message": "No active session found. Please log in again.", "code": "AUTH_SESSION_NOT_FOUND", "requiresLogout": true } { "message": "Your session has been revoked. Please log in again.", "code": "AUTH_SESSION_REVOKED", "requiresLogout": true } { "message": "Your session has expired. Please log in again.", "code": "AUTH_REFRESH_EXPIRED", "requiresLogout": true } { "message": "User account issue. Please log in again.", "code": "AUTH_USER_NOT_FOUND", "requiresLogout": true } { "message": "Authentication error. Please log in again.", "code": "AUTH_ERROR", "requiresLogout": true }
/api/auth/register
Register a new user account
Request Body:
{ "username": "johnsmith", "email": "john@example.com", "password": "securePassword123", "firstName": "John", "lastName": "Smith" }
Field Descriptions:
username
(required) - User's display name, will be trimmed of whitespaceemail
(required) - Valid email address, must be uniquepassword
(required) - Must be at least 8 characters longfirstName
(optional) - User's first name, max 50 characterslastName
(optional) - User's last name, max 50 charactersResponse (201 Created):
{ "token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...", // Valid for 30 minutes "refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...", // Valid for 6 months "csrfToken": "f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3", "expiresIn": 1800, // 30 minutes in seconds "user": { "id": 2, "username": "johnsmith", "email": "john@example.com", "typeCode": "SUBS", "typeName": "Subscribed", "firstName": "John", "lastName": "Smith", "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "verifiedAt": null, "updatedAt": null, "subscription": null } }
Error Responses:
{ "message": "Email address is already registered", "code": "AUTH_EMAIL_EXISTS" } { "message": "Validation error", "code": "VALIDATION_ERROR", "errors": [ { "type": "field", "value": "", "msg": "Username is required", "path": "username", "location": "body" } ] }
Notes:
/api/auth/reset-password
Request a password reset code
Request Body:
{ "email": "user@example.com" }
Response (200 OK):
{ "message": "Password reset code has been sent to your email address" }
Error Responses:
{ "message": "Email address not found" }
Notes:
/api/auth/verify-password
Verify user's current password (requires authentication and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{ "password": "currentUserPassword" }
Response (200 OK):
{ "message": "Password verified successfully" }
Error Responses:
{ "message": "Authentication required" } { "message": "Password is required" } { "message": "Invalid password" } { "message": "Account has been deactivated" } { "message": "Password verification is not available for Google accounts" }
Notes:
/api/auth/verify-registration
Verify a registration with the verification code
Request Body:
{ "email": "user@example.com", "verificationCode": "123456" }
Response (200 OK):
{ "message": "Email verified successfully" }
Error Responses:
{ "message": "Invalid email address" } { "message": "No verification request found" } { "message": "Maximum attempts exceeded" } { "message": "Verification code has expired" } { "message": "Invalid verification code" }
Notes:
/api/auth/verify-reset-password
Reset password using verification code
POST/api/auth/verify-reset-password
Reset password using verification code
Request Body:
{ "email": "user@example.com", "verificationCode": "123456", "newPassword": "newSecurePassword" }
Response (200 OK):
{ "message": "Password has been successfully reset" }
Error Responses:
{ "message": "Password must be at least 8 characters long" } { "message": "Invalid email address" } { "message": "No password reset request found" } { "message": "Maximum attempts exceeded. Please request a new verification code" } { "message": "Verification code has expired. Please request a new one" } { "message": "Invalid verification code" }
Notes: