Authentication API
Complete reference for user authentication, registration, and token management.
Overview
ModestWear implements a hybrid authentication system:
Traditional: Email/password with JWT tokens
Social OAuth: Google and Facebook login
Email Verification: Required for full access
Security: Account lockout, token blacklisting, password validation
Endpoints Summary
Endpoint |
Method |
Auth Required |
Description |
|---|---|---|---|
|
POST |
No |
Create new account |
|
POST |
No |
Authenticate user |
|
POST |
Yes |
Invalidate tokens |
|
POST |
No |
Verify email address |
|
POST |
No |
Refresh access token |
|
GET |
Yes |
Validate current token |
|
POST |
No |
Google OAuth login |
|
POST |
No |
Facebook OAuth login |
|
GET |
Yes |
Get user profile |
|
PUT/PATCH |
Yes |
Update profile |
Registration
POST /api/users/register/
Create a new user account with email and password.
Authentication: Not required
Request Body:
Field |
Type |
Required |
Description |
|---|---|---|---|
string |
Yes |
Valid email address (unique) |
|
password |
string |
Yes |
Min 8 chars, not too common |
full_name |
string |
Yes |
Auto-split into first/last name |
phone_number |
string |
No |
Contact number |
Password Requirements:
Minimum 8 characters
Cannot be too similar to email/username
Cannot be entirely numeric
Cannot be a commonly used password
Example Request:
curl -X POST https://modestwear.onrender.com/api/users/register/ \
-H "Content-Type: application/json" \
-d '{
"email": "sarah@example.com",
"password": "SecurePass123!",
"full_name": "Sarah Ahmed",
"phone_number": "+27123456789"
}'
Success Response (201 Created):
{
"success": true,
"data": {
"user": {
"id": 15,
"email": "sarah@example.com",
"username": "sarah",
"first_name": "Sarah",
"last_name": "Ahmed",
"phone_number": "+27123456789",
"profile_picture": null,
"profile_picture_url": null,
"created_at": "2024-01-20T14:30:00Z"
},
"tokens": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 1209600
},
"is_new_user": true,
"email_verified": false
}
}
Business Logic:
Validates email format and uniqueness
Validates password against Django’s validators
Auto-generates username from email prefix
Sets
is_verified=falseinitiallySends verification email asynchronously
Returns JWT tokens immediately (user can browse)
Logs registration event with IP address
Error Responses:
Email Already Exists (400):
{
"success": false,
"error": "A user with this email already exists."
}
Weak Password (400):
{
"success": false,
"error": "This password is too common., This password is entirely numeric."
}
Missing Fields (400):
{
"success": false,
"error": "Email, password, and full name are required"
}
Email Verification
POST /api/users/verify-email/
Verify user’s email address using token from email.
Authentication: Not required
Request Body:
Field |
Type |
Required |
Description |
|---|---|---|---|
token |
string |
Yes |
Verification token from email |
Example Request:
curl -X POST https://modestwear.onrender.com/api/users/verify-email/ \
-H "Content-Type: application/json" \
-d '{
"token": "abc123xyz789..."
}'
Success Response (200 OK):
{
"success": true,
"message": "Email verified successfully",
"data": {
"email": "sarah@example.com",
"is_verified": true
}
}
Business Logic:
Decodes and validates token
Checks token expiration (72 hours)
Updates
is_verified=truein databaseClears verification token cache
Logs verification event
Token Format:
Base64 encoded JSON:
{"user_id": 15, "email": "sarah@example.com", "timestamp": 1705756800}Signed with SECRET_KEY
Expires after 72 hours (EMAIL_VERIFICATION_TIMEOUT)
Error Responses:
Invalid Token (400):
{
"success": false,
"error": "Invalid verification token"
}
Expired Token (400):
{
"success": false,
"error": "Verification token has expired"
}
Already Verified (400):
{
"success": false,
"error": "Email already verified"
}
Login
POST /api/users/login/
Authenticate user and receive JWT tokens.
Authentication: Not required
Request Body:
Field |
Type |
Required |
Description |
|---|---|---|---|
string |
Yes |
User’s email address |
|
password |
string |
Yes |
User’s password |
device_info |
object |
No |
Device metadata for audit |
Example Request:
curl -X POST https://modestwear.onrender.com/api/users/login/ \
-H "Content-Type: application/json" \
-d '{
"email": "sarah@example.com",
"password": "SecurePass123!",
"device_info": {
"device_type": "mobile",
"os": "iOS 17",
"app_version": "1.0.0"
}
}'
Success Response (200 OK):
{
"data": {
"user": {
"id": 15,
"email": "sarah@example.com",
"username": "sarah",
"first_name": "Sarah",
"last_name": "Ahmed",
"phone_number": "+27123456789",
"profile_picture_url": "https://res.cloudinary.com/.../profile.jpg",
"created_at": "2024-01-20T14:30:00Z"
},
"tokens": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 1209600
},
"email_verified": true,
"verification_needed": false
}
}
Business Logic:
Checks for account lockout (Redis cache)
Authenticates credentials with Django’s auth system
Validates account is active
Clears failed login attempts on success
Updates
last_logintimestampGenerates new JWT token pair
Logs successful login with IP and user agent
Returns user profile and tokens
Security Features:
Rate Limiting: 5 failed attempts = 15-minute lockout
Failed Attempt Tracking: Stored in Redis with 30-minute TTL
IP Logging: All login attempts logged with IP address
Device Tracking: Optional device_info for audit trail
Error Responses:
Invalid Credentials (401):
{
"success": false,
"error": "Invalid email or password"
}
Account Locked (403):
{
"success": false,
"error": "Account temporarily locked due to multiple failed attempts. Try again later.",
"lockout": true
}
Account Disabled (403):
{
"success": false,
"error": "Account is disabled. Please contact support."
}
Unverified Email (200 with flag):
{
"data": {
"user": {...},
"tokens": {...},
"email_verified": false,
"verification_needed": true
}
}
Token Refresh
POST /api/users/token/refresh/
Get new access token using refresh token.
Authentication: Not required (uses refresh token)
Request Body:
Field |
Type |
Required |
Description |
|---|---|---|---|
refresh_token |
string |
Yes |
Valid refresh token |
Example Request:
curl -X POST https://modestwear.onrender.com/api/users/token/refresh/ \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}'
Success Response (200 OK):
{
"success": true,
"data": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 900
}
}
Business Logic:
Validates refresh token signature
Checks token not blacklisted
Checks token not expired (14 days)
Generates new access token (15 minutes)
Rotates refresh token (new one issued)
Blacklists old refresh token
Returns new token pair
Token Rotation:
ROTATE_REFRESH_TOKENS=True: New refresh token on each refreshBLACKLIST_AFTER_ROTATION=True: Old token blacklistedPrevents token reuse attacks
Error Responses:
Invalid Token (401):
{
"detail": "Token is invalid or expired",
"code": "token_not_valid"
}
Blacklisted Token (401):
{
"detail": "Token is blacklisted",
"code": "token_not_valid"
}
Token Validation
GET /api/users/token/validate/
Validate current access token and get user info.
Authentication: Required (Bearer token)
Example Request:
curl https://modestwear.onrender.com/api/users/token/validate/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
Success Response (200 OK):
{
"success": true,
"data": {
"valid": true,
"user_id": 15,
"email_verified": true
}
}
Business Logic:
Extracts token from Authorization header
Validates token signature and expiration
Checks user_id matches authenticated user
Retrieves verification status (cached)
Returns validation result
Use Cases:
Frontend token validation on app load
Check if token still valid before API calls
Verify email verification status
Logout
POST /api/users/logout/
Invalidate refresh token and log out user.
Authentication: Required (Bearer token)
Request Body:
Field |
Type |
Required |
Description |
|---|---|---|---|
refresh_token |
string |
Optional |
Token to blacklist |
Example Request:
curl -X POST https://modestwear.onrender.com/api/users/logout/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}'
Success Response (200 OK):
{
"success": true,
"message": "Successfully logged out"
}
Business Logic:
Extracts JTI (JWT ID) from refresh token
Adds JTI to blacklist (Redis + database)
Logs logout event
Clears HTTP-only cookie if used
Returns success message
Note: Access token remains valid until expiration (15 minutes). Frontend should discard it immediately.
Google OAuth
User Profile
GET /api/users/profile/
Get authenticated user’s profile.
Authentication: Required
Example Request:
curl https://modestwear.onrender.com/api/users/profile/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
Success Response (200 OK):
{
"id": 15,
"email": "sarah@example.com",
"username": "sarah",
"first_name": "Sarah",
"last_name": "Ahmed",
"phone_number": "+27123456789",
"profile_picture": "users/profiles/sarah_abc123.jpg",
"profile_picture_url": "https://res.cloudinary.com/.../sarah_abc123.jpg",
"created_at": "2024-01-20T14:30:00Z"
}
PUT/PATCH /api/users/profile/
Update user profile.
Authentication: Required
Request Body:
Field |
Type |
Description |
|---|---|---|
first_name |
string |
User’s first name |
last_name |
string |
User’s last name |
phone_number |
string |
Contact number |
profile_picture |
file |
Image file (multipart/form-data) |
Example Request:
curl -X PATCH https://modestwear.onrender.com/api/users/profile/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"first_name": "Sarah",
"last_name": "Ahmed Khan",
"phone_number": "+27987654321"
}'
Success Response (200 OK):
{
"id": 15,
"email": "sarah@example.com",
"username": "sarah",
"first_name": "Sarah",
"last_name": "Ahmed Khan",
"phone_number": "+27987654321",
"profile_picture_url": "https://res.cloudinary.com/.../sarah_abc123.jpg",
"created_at": "2024-01-20T14:30:00Z"
}
Business Logic:
Email and username cannot be changed
Profile picture uploaded to Cloudinary
Old profile picture deleted from Cloudinary
Thumbnail auto-generated
JWT Token Structure
Access Token Payload
{
"token_type": "access",
"exp": 1705757700,
"iat": 1705756800,
"jti": "abc123xyz789",
"user_id": 15
}
Refresh Token Payload
{
"token_type": "refresh",
"exp": 1706966400,
"iat": 1705756800,
"jti": "def456uvw012",
"user_id": 15
}
Token Lifetimes:
Access: 15 minutes (900 seconds)
Refresh: 14 days (1,209,600 seconds)
Algorithm: HS256 (HMAC with SHA-256)
Security Best Practices
Store tokens securely - Use httpOnly cookies or secure storage
Refresh proactively - Refresh 1-2 minutes before expiration
Handle 401 errors - Redirect to login when unauthorized
Use HTTPS only - Never send tokens over HTTP
Logout on sensitive actions - Clear tokens on password change
Validate on app load - Check token validity on startup
Don’t log tokens - Never log full tokens in frontend
Next Steps
Explore Catalog API
Learn about Orders API