Jiffoo Docs

Authentication API

Complete guide to JWT-based authentication in Jiffoo Mall

Authentication API

Jiffoo Mall uses JWT (JSON Web Tokens) for authentication. The API supports both access tokens and refresh tokens following OAuth2 standards.

Authentication Flow

sequenceDiagram
    Client->>API: POST /auth/register or /auth/login
    API->>API: Validate credentials
    API->>Client: Return access_token + refresh_token
    Client->>API: Request with Authorization: Bearer <token>
    API->>Client: Return protected resource
    Note over Client,API: When access token expires
    Client->>API: POST /auth/refresh with refresh_token
    API->>Client: Return new access_token + refresh_token

Endpoints

Register User

Create a new user account.

Endpoint: POST /auth/register

Request Body:

{
  "email": "[email protected]",
  "username": "johndoe",
  "password": "SecurePass123!",
  "referralCode": "OPTIONAL_CODE"
}

Response: 201 Created

{
  "success": true,
  "message": "Registration successful",
  "data": {
    "user": {
      "id": "cm52kg0000000qv0akj8r9w3d",
      "email": "[email protected]",
      "username": "johndoe",
      "role": "USER",
      "avatar": null
    },
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "Bearer",
    "expires_in": 604800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Response: 400 Bad Request

{
  "success": false,
  "error": {
    "code": "REGISTRATION_FAILED",
    "message": "User with this email or username already exists"
  }
}

Validation Rules:

  • email: Valid email format
  • username: Minimum 3 characters
  • password: Minimum 6 characters
  • referralCode: Optional string

Login

Authenticate a user and receive JWT tokens.

Endpoint: POST /auth/login

Request Body:

{
  "email": "[email protected]",
  "password": "SecurePass123!"
}

Response: 200 OK

{
  "success": true,
  "data": {
    "user": {
      "id": "cm52kg0000000qv0akj8r9w3d",
      "email": "[email protected]",
      "username": "johndoe",
      "role": "USER",
      "avatar": "https://example.com/avatar.jpg"
    },
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbTUya2cwMDAwMDAwcXYwYWtqOHI5dzNkIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwicm9sZSI6IlVTRVIiLCJpYXQiOjE3MDcwMDAwMDAsImV4cCI6MTcwNzYwNDgwMH0.example_signature",
    "token_type": "Bearer",
    "expires_in": 604800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbTUya2cwMDAwMDAwcXYwYWtqOHI5dzNkIiwidHlwZSI6InJlZnJlc2giLCJpYXQiOjE3MDcwMDAwMDAsImV4cCI6MTcwOTU5MjAwMH0.example_signature",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Response: 401 Unauthorized

{
  "success": false,
  "error": {
    "code": "LOGIN_FAILED",
    "message": "Invalid email or password"
  }
}

Get Current User

Retrieve authenticated user information.

Endpoint: GET /auth/me

Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response: 200 OK

{
  "success": true,
  "data": {
    "id": "cm52kg0000000qv0akj8r9w3d",
    "email": "[email protected]",
    "username": "johndoe",
    "role": "USER",
    "avatar": "https://example.com/avatar.jpg",
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
}

Error Response: 401 Unauthorized

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired token"
  }
}

Refresh Token

Obtain a new access token using a refresh token.

Endpoint: POST /auth/refresh

Request Body:

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response: 200 OK

{
  "success": true,
  "data": {
    "user": {
      "id": "cm52kg0000000qv0akj8r9w3d",
      "email": "[email protected]",
      "username": "johndoe",
      "role": "USER",
      "avatar": "https://example.com/avatar.jpg"
    },
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "Bearer",
    "expires_in": 604800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Response: 401 Unauthorized

{
  "success": false,
  "error": {
    "code": "REFRESH_FAILED",
    "message": "Invalid refresh token"
  }
}

Logout

Logout the current user (client-side token removal).

Endpoint: POST /auth/logout

Response: 200 OK

{
  "success": true,
  "data": null,
  "message": "Logged out successfully"
}

Note: Logout is primarily handled client-side by removing tokens from storage. The server endpoint provides a confirmation response.


Change Password

Change the password for the authenticated user.

Endpoint: POST /auth/change-password

Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Request Body:

{
  "currentPassword": "OldPass123!",
  "newPassword": "NewSecurePass456!"
}

Response: 200 OK

{
  "success": true,
  "data": null,
  "message": "Password changed successfully"
}

Error Responses:

404 Not Found:

{
  "success": false,
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User not found"
  }
}

400 Bad Request:

{
  "success": false,
  "error": {
    "code": "INVALID_PASSWORD",
    "message": "Current password is incorrect"
  }
}

Validation Rules:

  • newPassword: Minimum 6 characters

JWT Token Structure

Access Token Payload

{
  "userId": "cm52kg0000000qv0akj8r9w3d",
  "email": "[email protected]",
  "role": "USER",
  "iat": 1707000000,
  "exp": 1707604800
}

Fields:

  • userId: Unique user identifier
  • email: User's email address
  • role: User role (USER, ADMIN, etc.)
  • iat: Issued at timestamp
  • exp: Expiration timestamp

Refresh Token Payload

{
  "userId": "cm52kg0000000qv0akj8r9w3d",
  "type": "refresh",
  "iat": 1707000000,
  "exp": 1709592000
}

Fields:

  • userId: Unique user identifier
  • type: Token type indicator ("refresh")
  • iat: Issued at timestamp
  • exp: Expiration timestamp (typically longer than access token)

Token Lifecycle

Access Token

  • Duration: 7 days (604,800 seconds)
  • Use: Authorization for protected endpoints
  • Storage: Memory, localStorage, or httpOnly cookie
  • Refresh: Use refresh token when expired

Refresh Token

  • Duration: 30 days (2,592,000 seconds)
  • Use: Obtain new access tokens
  • Storage: httpOnly cookie (recommended) or secure storage
  • Rotation: New refresh token issued on each refresh

Usage Examples

JavaScript/TypeScript (Fetch API)

Login Example

async function login(email: string, password: string) {
  const response = await fetch('http://localhost:3001/auth/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ email, password }),
  });

  const result = await response.json();

  if (result.success) {
    // Store tokens securely
    localStorage.setItem('access_token', result.data.access_token);
    localStorage.setItem('refresh_token', result.data.refresh_token);
    return result.data;
  } else {
    throw new Error(result.error.message);
  }
}

Authenticated Request Example

async function fetchProtectedResource() {
  const token = localStorage.getItem('access_token');

  const response = await fetch('http://localhost:3001/auth/me', {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });

  if (response.status === 401) {
    // Token expired, refresh it
    await refreshAccessToken();
    return fetchProtectedResource(); // Retry
  }

  return response.json();
}

Token Refresh Example

async function refreshAccessToken() {
  const refreshToken = localStorage.getItem('refresh_token');

  const response = await fetch('http://localhost:3001/auth/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });

  const result = await response.json();

  if (result.success) {
    localStorage.setItem('access_token', result.data.access_token);
    localStorage.setItem('refresh_token', result.data.refresh_token);
    return result.data.access_token;
  } else {
    // Refresh failed, redirect to login
    localStorage.clear();
    window.location.href = '/login';
  }
}

cURL Examples

Register

curl -X POST http://localhost:3001/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "username": "johndoe",
    "password": "SecurePass123!"
  }'

Login

curl -X POST http://localhost:3001/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "SecurePass123!"
  }'

Get Current User

curl -X GET http://localhost:3001/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Refresh Token

curl -X POST http://localhost:3001/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

Change Password

curl -X POST http://localhost:3001/auth/change-password \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "currentPassword": "OldPass123!",
    "newPassword": "NewSecurePass456!"
  }'

Security Best Practices

Token Storage

✅ Recommended:

  • Store refresh tokens in httpOnly cookies
  • Store access tokens in memory or secure storage
  • Never store tokens in localStorage for production apps

❌ Avoid:

  • Storing tokens in localStorage (XSS vulnerability)
  • Logging tokens to console
  • Transmitting tokens via URL parameters

Token Validation

Always validate tokens on the server side:

  • Verify signature
  • Check expiration
  • Validate token type (access vs refresh)
  • Verify user still exists and is active

HTTPS Only

Always use HTTPS in production:

// Production configuration
const API_URL = 'https://api.yourdomain.com';

Token Rotation

The API automatically rotates refresh tokens on each refresh request, providing enhanced security.


Error Handling

Common Error Codes

CodeStatusDescriptionSolution
REGISTRATION_FAILED400User already existsUse different email/username
LOGIN_FAILED401Invalid credentialsCheck email and password
UNAUTHORIZED401Invalid/expired tokenRefresh token or re-login
REFRESH_FAILED401Invalid refresh tokenRe-login required
USER_NOT_FOUND404User doesn't existCheck user ID
INVALID_PASSWORD400Current password wrongVerify current password
CHANGE_PASSWORD_FAILED500Server errorTry again or contact support

Handling Token Expiration

// Axios interceptor example
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        const newToken = await refreshAccessToken();
        originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
        return axios(originalRequest);
      } catch (refreshError) {
        // Redirect to login
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

Service-to-Service Authentication

For machine-to-machine communication, the API supports service JWT tokens:

// Service JWT payload
{
  "sub": "service-account-id",
  "iss": "jiffoo-platform",
  "iat": 1707000000,
  "exp": 1707604800
}

Service accounts are automatically granted ADMIN role and full permissions when the issuer matches the configured SERVICE_JWT_ISSUER.


Rate Limiting

Authentication endpoints are rate limited:

EndpointLimit
/auth/register5 requests/hour per IP
/auth/login10 requests/minute per IP
/auth/refresh60 requests/hour per token
/auth/me100 requests/minute per user
/auth/change-password5 requests/hour per user

Exceeding rate limits returns:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again later.",
    "retryAfter": 3600
  }
}

Next Steps

On this page