Authentication Endpoints Index

Token-Based Authentication

This API uses secure opaque tokens for authentication. These tokens are cryptographically secure random strings that are validated against database sessions.

How Token Authentication Works

  1. When you successfully log in or register, the server generates a secure opaque access token.
  2. This token must be included in the Authorization header as a Bearer token for all protected endpoints.
  3. The token is validated against active sessions stored in the database.
  4. Access tokens have an expiration time of 30 minutes from issuance.
  5. Refresh tokens are valid for 6 months and can be used to obtain new access tokens.
  6. When an access token expires, you can use the refresh token endpoint to obtain a new valid token.
  7. Sessions persist across server restarts, ensuring uninterrupted authentication.

Example authorization header:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Token Types and Persistent Authentication

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

Persistent Login Flow

For persistent authentication, clients should implement automatic token refresh:

  1. Initial Login: Receive access token (30min), refresh token (6mo), and CSRF token
  2. API Requests: Use access token in Authorization header for all API calls
  3. Token Expiry Detection: Monitor for 401 responses indicating expired access token
  4. Automatic Refresh: Use refresh token to obtain new access token via /api/auth/refresh
  5. Sliding Window: Each refresh extends the refresh token expiration (sliding window)
  6. Seamless Experience: Users stay logged in for up to 6 months without re-entering credentials

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();
    }
}

Client-Side Implementation

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:

CSRF Protection

This API implements CSRF (Cross-Site Request Forgery) protection for all mutation operations (POST, PUT, PATCH, DELETE).

How CSRF Protection Works

  1. When you log in or register, the server returns a CSRF token along with your authentication token.
  2. You must include this CSRF token in the x-csrf-token header for all subsequent mutation requests.
  3. Requests without a valid CSRF token will be rejected with a 403 Forbidden response.
  4. The CSRF token is validated against the token stored in your active session in the database.
  5. CSRF tokens are tied to your session validity and will become invalid when your session expires or you log out.
  6. Authentication endpoints themselves are exempt from CSRF protection.

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": "REGU",
        "typeName": "Regular",
        "firstName": "John",
        "lastName": "Doe",
        "isActive": true,
        "createdAt": "2023-01-15T08:30: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

Authentication Endpoints

POST /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": "REGU",
        "typeName": "Regular",
        "firstName": "John",
        "lastName": "Doe",
        "isActive": true,
        "createdAt": "2023-01-15T08:30: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"
}
POST /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"
}
POST /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:

Response (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": "REGU",
        "typeName": "Regular",
        "firstName": "John",
        "lastName": "Doe",
        "isActive": true,
        "createdAt": "2023-01-15T08:30: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
}
POST /api/auth/register

Register a new user account

Request Body:

{
    "username": "johnsmith",
    "email": "john@example.com",
    "password": "securePassword123",
    "firstName": "John",
    "lastName": "Smith"
}

Field Descriptions:

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": "johnsmith",
        "email": "john@example.com",
        "typeCode": "REGU",
        "typeName": "Regular",
        "firstName": "John",
        "lastName": "Smith",
        "isActive": true,
        "createdAt": "2023-01-15T08:30:00Z",
        "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:

POST /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:

POST /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:

POST /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: