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_tokenEndpoints
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 formatusername: Minimum 3 characterspassword: Minimum 6 charactersreferralCode: 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 identifieremail: User's email addressrole: User role (USER, ADMIN, etc.)iat: Issued at timestampexp: Expiration timestamp
Refresh Token Payload
{
"userId": "cm52kg0000000qv0akj8r9w3d",
"type": "refresh",
"iat": 1707000000,
"exp": 1709592000
}Fields:
userId: Unique user identifiertype: Token type indicator ("refresh")iat: Issued at timestampexp: 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
| Code | Status | Description | Solution |
|---|---|---|---|
REGISTRATION_FAILED | 400 | User already exists | Use different email/username |
LOGIN_FAILED | 401 | Invalid credentials | Check email and password |
UNAUTHORIZED | 401 | Invalid/expired token | Refresh token or re-login |
REFRESH_FAILED | 401 | Invalid refresh token | Re-login required |
USER_NOT_FOUND | 404 | User doesn't exist | Check user ID |
INVALID_PASSWORD | 400 | Current password wrong | Verify current password |
CHANGE_PASSWORD_FAILED | 500 | Server error | Try 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:
| Endpoint | Limit |
|---|---|
/auth/register | 5 requests/hour per IP |
/auth/login | 10 requests/minute per IP |
/auth/refresh | 60 requests/hour per token |
/auth/me | 100 requests/minute per user |
/auth/change-password | 5 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
- API Reference - Complete API documentation
- Product API - Product management endpoints
- Cart API - Shopping cart operations
- Order API - Order processing endpoints