Authentication
Base URL: https://api.fursahub.com/api/v1
Short Description: The Authentication API handles user authentication for Fursa Hub using Firebase Authentication as the identity provider. It supports Google Sign-In, Apple Sign-In, and Email/Password authentication. After Firebase verification, the API issues custom JWT tokens (access and refresh tokens) for subsequent API calls.
Hints:
- Firebase ID tokens expire after 1 hour - always verify before calling the authenticate endpoint
- Access tokens are valid for 1 hour; refresh tokens are valid for 30 days
- New users are automatically created on first authentication with onboarding status set to
PENDING_PHONE_VERIFICATION - All protected endpoints require Bearer token authentication via the
Authorizationheader - Supported authentication providers:
GOOGLE,APPLE,EMAIL - A unique username is auto-generated from the user's email on first registration
Authentication Flow
Initial Authentication (Login/Register)
Understanding this flow is critical for frontend integration:
- User signs in via Firebase (Google, Apple, or Email) on the mobile/web client
- Firebase returns an ID token to the client
- Client sends Firebase ID token to
POST /api/v1/auth/firebase/authenticate - Backend verifies Firebase token, creates user if new (or retrieves existing), and returns custom JWT tokens
- Client stores tokens securely - access token for API calls, refresh token for renewal
- Check onboarding status in response to determine if user needs to complete onboarding steps
Token Refresh Flow
- When access token expires (you receive
401 UNAUTHORIZED), callPOST /api/v1/auth/refresh - Backend validates refresh token and issues a new access token (same refresh token is returned)
- Client updates stored access token and retries the failed request
Logout Flow
- Client calls
POST /api/v1/auth/logoutwith valid access token - Backend revokes all refresh tokens for the user
- Client clears stored tokens locally
Standard Response Format
All API responses follow a consistent structure using our Globe Response Builder pattern:
Success Response Structure
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2025-01-02T10:30:45",
"data": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-01-02T10:30:45",
"data": "Error description"
}
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true for successful operations, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable message describing the operation result |
action_time |
string | ISO 8601 timestamp of when the response was generated |
data |
object/string | Response payload for success, error details for failures |
Endpoints
1. Authenticate with Firebase
Purpose: Authenticate user with Firebase ID token. Creates new user on first login or returns existing user.
Endpoint: POST {base_url}/auth/firebase/authenticate
Access Level: 🌐 Public (No authentication required)
Authentication: None
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Content-Type | string | Yes | Must be application/json |
Request JSON Sample:
{
"firebaseToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"preferredLanguage": "en",
"deviceInfo": "iPhone 14 Pro, iOS 17.2"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
firebaseToken |
string | Yes | Firebase ID token obtained from Firebase Auth SDK | Must not be blank |
preferredLanguage |
string | No | User's preferred language code | Valid codes: en, sw, fr, zh. Defaults to en |
deviceInfo |
string | No | Device information for security tracking | Max 255 characters |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Authentication successful",
"action_time": "2025-01-02T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl...",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@gmail.com",
"username": "johndoe",
"phoneNumber": null,
"fullName": "John Doe",
"profilePhotoUrl": "https://lh3.googleusercontent.com/a/photo.jpg",
"isPhoneVerified": false,
"isEmailVerified": true,
"preferredLanguage": "en",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"createdAt": "2025-01-02T10:30:45"
},
"onboarding": {
"isComplete": false,
"currentStep": "PENDING_PHONE_VERIFICATION"
}
}
}
Success Response Fields:
| Field | Description |
|---|---|
accessToken |
JWT access token for API authentication (valid for 1 hour) |
refreshToken |
JWT refresh token for obtaining new access tokens (valid for 30 days) |
tokenType |
Token type, always "Bearer" |
expiresIn |
Access token expiration time in seconds |
user.id |
Unique user identifier (UUID) |
user.email |
User's email address from Firebase |
user.username |
Auto-generated unique username |
user.phoneNumber |
User's phone number (null until verified) |
user.fullName |
User's display name from Firebase |
user.profilePhotoUrl |
Profile photo URL from Firebase provider |
user.isPhoneVerified |
Phone verification status |
user.isEmailVerified |
Email verification status from Firebase |
user.preferredLanguage |
User's language preference |
user.authProvider |
Authentication provider: GOOGLE, APPLE, or EMAIL |
user.role |
User's role in the system |
user.createdAt |
Account creation timestamp |
onboarding.isComplete |
Whether user has completed all onboarding steps |
onboarding.currentStep |
Current onboarding step the user is on |
Onboarding Status Values:
| Status | Description | Next Action |
|---|---|---|
PENDING_PHONE_VERIFICATION |
User needs to verify phone number | Direct to phone verification screen |
PENDING_PREFERENCES |
User needs to complete preference pages | Direct to onboarding preferences |
PENDING_PROFILE_COMPLETION |
User needs to complete profile | Direct to profile completion |
COMPLETED |
Onboarding complete | Direct to home/dashboard |
Error Response JSON Samples:
Invalid or Expired Firebase Token (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Invalid or expired Firebase token",
"action_time": "2025-01-02T10:30:45",
"data": "Invalid or expired Firebase token"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-01-02T10:30:45",
"data": {
"firebaseToken": "Firebase token is required"
}
}
2. Refresh Access Token
Purpose: Obtain a new access token using a valid refresh token.
Endpoint: POST {base_url}/auth/refresh
Access Level: 🌐 Public (No authentication required, but requires valid refresh token in body)
Authentication: None (refresh token is passed in request body)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Content-Type | string | Yes | Must be application/json |
Request JSON Sample:
{
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl..."
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
refreshToken |
string | Yes | Valid refresh token obtained from authentication | Must not be blank |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Token refreshed successfully",
"action_time": "2025-01-02T11:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl...",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@gmail.com",
"username": "johndoe",
"phoneNumber": "+255712345678",
"fullName": "John Doe",
"profilePhotoUrl": "https://lh3.googleusercontent.com/a/photo.jpg",
"isPhoneVerified": true,
"isEmailVerified": true,
"preferredLanguage": "en",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"createdAt": "2025-01-02T10:30:45"
},
"onboarding": {
"isComplete": true,
"currentStep": "COMPLETED"
}
}
}
Success Response Fields:
| Field | Description |
|---|---|
accessToken |
New JWT access token (valid for 1 hour) |
refreshToken |
Same refresh token (unchanged) |
tokenType |
Token type, always "Bearer" |
expiresIn |
Access token expiration time in seconds |
user |
Current user profile data |
onboarding |
Current onboarding status |
Error Response JSON Samples:
Invalid Refresh Token (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Invalid refresh token",
"action_time": "2025-01-02T10:30:45",
"data": "Invalid refresh token"
}
Expired Refresh Token (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Refresh token expired",
"action_time": "2025-01-02T10:30:45",
"data": "Refresh token expired"
}
Account Inactive or Locked (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Account is inactive or locked",
"action_time": "2025-01-02T10:30:45",
"data": "Account is inactive or locked"
}
3. Logout
Purpose: Logout user and revoke all refresh tokens for the account.
Endpoint: POST {base_url}/auth/logout
Access Level: 🔒 Protected (Requires valid access token)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token: Bearer {accessToken} |
Request JSON Sample: No request body required
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Logged out successfully",
"action_time": "2025-01-02T12:00:00",
"data": null
}
Success Response Fields:
| Field | Description |
|---|---|
data |
null (no data returned on logout) |
Error Response JSON Samples:
Missing or Invalid Token (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token is missing or invalid",
"action_time": "2025-01-02T10:30:45",
"data": "Token is missing or invalid"
}
Expired Token (401):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2025-01-02T10:30:45",
"data": "Token has expired"
}
4. Get Current User
Purpose: Retrieve the currently authenticated user's profile information.
Endpoint: GET {base_url}/auth/me
Access Level: 🔒 Protected (Requires valid access token)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token: Bearer {accessToken} |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "User retrieved successfully",
"action_time": "2025-01-02T10:30:45",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@gmail.com",
"username": "johndoe",
"phoneNumber": "+255712345678",
"fullName": "John Doe",
"profilePhotoUrl": "https://lh3.googleusercontent.com/a/photo.jpg",
"isPhoneVerified": true,
"isEmailVerified": true,
"preferredLanguage": "en",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"createdAt": "2025-01-02T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
id |
Unique user identifier (UUID) |
email |
User's email address |
username |
Unique username |
phoneNumber |
Verified phone number in E.164 format (e.g., +255712345678) |
fullName |
User's display name |
profilePhotoUrl |
URL to user's profile photo |
isPhoneVerified |
Whether phone number is verified |
isEmailVerified |
Whether email is verified (from Firebase) |
preferredLanguage |
User's preferred language code |
authProvider |
Original authentication provider |
role |
User's role: ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN, ROLE_SUPER_ADMIN |
createdAt |
Account creation timestamp |
Error Response JSON Samples:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token is missing or invalid",
"action_time": "2025-01-02T10:30:45",
"data": "Token is missing or invalid"
}
Standard Error Types
Application-Level Exceptions (400-499)
| Status Code | HTTP Status | When It Occurs |
|---|---|---|
| 400 | BAD_REQUEST | General invalid request data, malformed JSON |
| 401 | UNAUTHORIZED | Token empty, invalid, expired, or malformed; Firebase auth failed |
| 403 | FORBIDDEN | Account locked, verification required |
| 404 | NOT_FOUND | Requested resource does not exist |
| 409 | CONFLICT | Resource already exists (duplicate) |
| 422 | UNPROCESSABLE_ENTITY | Validation errors with detailed field information |
| 429 | TOO_MANY_REQUESTS | Rate limit exceeded |
Server-Level Exceptions (500+)
| Status Code | HTTP Status | When It Occurs |
|---|---|---|
| 500 | INTERNAL_SERVER_ERROR | Unexpected server errors |
Frontend Implementation Guide
Token Storage Recommendations
- Mobile (iOS/Android): Use secure storage (Keychain for iOS, EncryptedSharedPreferences for Android)
- Web: Use httpOnly cookies or secure localStorage with proper XSS protection
- Never store tokens in plain text or expose them in URLs
Handling Token Expiration
1. Make API request with access token
2. If 401 UNAUTHORIZED received:
a. Call /auth/refresh with refresh token
b. If refresh successful: Update stored access token, retry original request
c. If refresh fails (401): Clear all tokens, redirect to login
3. Continue with response
Handling Onboarding Status
After successful authentication, check onboarding.currentStep:
PENDING_PHONE_VERIFICATION→ Navigate to phone verification screenPENDING_PREFERENCES→ Navigate to preference selection screensPENDING_PROFILE_COMPLETION→ Navigate to profile completion screenCOMPLETED→ Navigate to main app/dashboard
Quick Reference
Authentication Header Format
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Supported Languages
| Code | Language |
|---|---|
en |
English |
sw |
Swahili |
fr |
French |
zh |
Chinese |
User Roles
| Role | Description |
|---|---|
ROLE_USER |
Standard user (default) |
ROLE_MODERATOR |
Content moderator |
ROLE_ADMIN |
Administrator |
ROLE_SUPER_ADMIN |
Super administrator with full access |