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/refreshToken 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/apple/callback
Handle Apple OAuth callback and authenticate or register user
Request Body:
{
"code": "c1234567890abcdef...",
"idToken": "eyJraWQiOiJXNldjT0tC...",
"nonce": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"redirectUri": "https://yourdomain.com/auth/apple/callback",
"mode": "login",
"platform": "ios"
}
Field Descriptions:
code (required) - Authorization code received from Apple OAuth flowidToken (optional) - Apple ID token (included in response_mode form_post)nonce (required) - Nonce used in the authorization request for replay attack preventionredirectUri (required) - Must match the redirect URI used in the authorization requestmode (required) - Authentication mode, either "login" or "register"platform (optional) - Platform identifier: "ios", "android", or "web" (defaults to "web")Response (200 OK for login, 201 Created for register):
{
"token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...",
"refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...",
"csrfToken": "a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3",
"expiresIn": 1800,
"user": {
"id": 1,
"username": "John Doe",
"email": "john@privaterelay.appleid.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:
{
"error": "Missing required parameters",
"message": "code, nonce, redirectUri, and mode are required",
"code": "APPLE_MISSING_PARAMS"
}
{
"error": "Invalid mode",
"message": "mode must be 'login' or 'register'",
"code": "APPLE_INVALID_MODE"
}
{
"error": "Invalid Apple ID token",
"message": "Failed to verify Apple ID token",
"code": "APPLE_INVALID_ID_TOKEN"
}
{
"error": "User not found",
"message": "No account found with this Apple ID. Please register first.",
"code": "AUTH_APPLE_USER_NOT_FOUND"
}
{
"error": "Email already exists",
"message": "An account with this Apple email already exists. Please sign in instead.",
"code": "AUTH_EMAIL_EXISTS"
}
{
"error": "Too many OAuth attempts",
"message": "Too many OAuth attempts from this IP, please try again later.",
"code": "RATE_LIMIT_EXCEEDED"
}
Notes:
Apple OAuth Flow Overview:
nonce and state valuesnonce and statecode, id_token, and statecode, id_token, and nonce to this endpoint/api/auth/apple/form-post
Handle Apple OAuth form_post callback (receives data from Apple and redirects to frontend)
Request Body (sent by Apple):
{
"code": "c1234567890abcdef...",
"id_token": "eyJraWQiOiJXNldjT0tC...",
"state": "{\"state\":\"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\",\"mode\":\"login\",\"returnUrl\":\"http://localhost:5173\"}",
"user": "{\"name\":{\"firstName\":\"John\",\"lastName\":\"Doe\"},\"email\":\"john@privaterelay.appleid.com\"}"
}
State Parameter Format:
The state parameter contains JSON-encoded data:
{
"state": "random_string_for_csrf_protection",
"mode": "login" | "register",
"returnUrl": "https://yourdomain.com" // Optional, frontend URL to redirect to
}
Response (200 OK - HTML redirect):
<!DOCTYPE html>
<html>
<head>
<title>Apple Sign In</title>
</head>
<body>
<p>Completing Apple Sign-In...</p>
<script>
window.location.replace('https://yourdomain.com/auth/apple/callback#code=...&id_token=...&state=...');
</script>
</body>
</html>
Error Response (400 Bad Request):
<!DOCTYPE html>
<html>
<body>
<script>
sessionStorage.setItem('appleAuthError', JSON.stringify({
title: 'Apple Authentication Error',
description: 'Missing required parameters from Apple',
type: 'error'
}));
window.location.replace('/login');
</script>
</body>
</html>
Notes:
Apple Developer Configuration:
In your Apple Services ID, configure the Return URL WITHOUT query parameters:
https://yourdomain.com/api/auth/apple/form-post
Important: Do NOT include query parameters like ?returnUrl=... in the Apple Developer Console configuration. Apple requires exact URL matching.
/api/auth/google/callback-pkce
Handle Google OAuth callback with PKCE (Proof Key for Code Exchange) - recommended for mobile apps
Request Body:
{
"code": "4/0AfJohXlxxx...",
"codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"redirectUri": "com.googleusercontent.apps.123456789:redirect",
"mode": "login",
"platform": "android"
}
Field Descriptions:
code (required) - Authorization code received from Google OAuth flowcodeVerifier (required) - PKCE code verifier (proves ownership of the auth request)redirectUri (required) - Must match the redirect URI used in the authorization requestmode (required) - Authentication mode, either "login" or "register"platform (optional) - Platform identifier: "ios", "android", or "web" (defaults to "web")Response (200 OK for login, 201 Created for register):
{
"token": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...",
"refreshToken": "XYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn...",
"csrfToken": "a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3",
"expiresIn": 1800,
"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:30:00Z",
"updatedAt": null,
"subscription": null
}
}
Error Responses:
{
"error": "Missing required parameters",
"message": "code, codeVerifier, redirectUri, and mode are required",
"code": "PKCE_MISSING_PARAMS"
}
{
"error": "Invalid mode",
"message": "mode must be 'login' or 'register'",
"code": "PKCE_INVALID_MODE"
}
{
"error": "Invalid redirect URI",
"message": "Redirect URI does not match expected pattern for platform",
"code": "PKCE_INVALID_REDIRECT_URI"
}
{
"error": "Failed to exchange authorization code",
"message": "Token exchange failed",
"code": "PKCE_TOKEN_EXCHANGE_FAILED"
}
{
"error": "User not found",
"message": "No account found with this Google email. Please register first.",
"code": "AUTH_GOOGLE_USER_NOT_FOUND"
}
{
"error": "Email already exists",
"message": "An account with this Google email already exists. Please sign in instead.",
"code": "AUTH_EMAIL_EXISTS"
}
{
"error": "Too many OAuth attempts",
"message": "Too many OAuth attempts from this IP, please try again later.",
"code": "RATE_LIMIT_EXCEEDED"
}
Notes:
PKCE Flow Overview:
codeVerifier (random string)codeChallenge from verifier (SHA256 hash)codeChallengecodecode + codeVerifier to this endpoint/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...",
"platform": "android" // Optional: "web", "android", or "ios"
}
Field Descriptions:
googleIdToken (required) - Google ID token obtained from OAuth flowplatform (optional) - Platform identifier for multi-platform support
"web" - Web application (default if not specified)"android" - Android application"ios" - iOS applicationResponse (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...",
"platform": "ios" // Optional: "web", "android", or "ios"
}
Field Descriptions:
googleIdToken (required) - Google ID token obtained from OAuth flowplatform (optional) - Platform identifier for multi-platform support
"web" - Web application (default if not specified)"android" - Android application"ios" - iOS applicationResponse (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: