Skip to main content

Authentication

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-01-0205
Version: v1.0

Base URL: https://api.fursahub.com/api/v1

Short Description: The Authentication API handles user authenticationendpoints for Fursa Hub usingplatform. FirebaseHandles Authenticationuser asregistration/login via Firebase, token management, and session control. All new users start the identityonboarding provider.flow Itafter supports Google Sign-In, Apple Sign-In, and Email/Passwordsuccessful authentication. After Firebase verification, the API issues custom JWT tokens (access and refresh tokens) for subsequent API calls.

Hints:

  • Firebase IDhandles tokensthe expireactual aftersign-in 1(Google, hourApple, Email) - alwaysyour verifyapp beforegets callinga theFirebase authenticateID endpointtoken
  • Send that Firebase token to our backend to get Fursa Hub access tokens
  • Access tokens areexpire valid forin 1 hour;hour, use refresh tokenstoken areto validget fornew 30 daysones
  • NewPass userspreferredLanguage areand automaticallytheme created onduring first authentication with onboarding statusto set touser PENDING_PHONE_VERIFICATION
  • All protected endpoints require Bearer token authentication via the Authorization header
  • Supported authentication providers: GOOGLE, APPLE, EMAIL
  • A unique username is auto-generated from the user's email on first registrationpreferences

Authentication Flow

Initial
┌─────────────────────────────────────────────────────────────────────────┐
Authentication│                        AUTHENTICATION FLOW                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. USER OPENS APP                                                       │
│     └── App shows language selector (Login/Register)

calls

UnderstandingGET this/languages) flow is critical└── User picks language (e.g., "sw" for frontendSwahili) integration:

  1. User└── signsApp instores vialanguage Firebase+ (Google,theme Apple,preference orlocally Email) on the mobile/web client
  2. 2.
  3. USER SIGNS IN VIA FIREBASE │ │ └── Google Sign-In / Apple Sign-In / Email+Password │ │ └── Firebase returns an ID tokenToken │ │ │ │ 3. APP SENDS TO FURSA HUB BACKEND │ │ └── POST /auth/firebase/authenticate │ │ └── Include: firebaseToken, preferredLanguage, theme │ │ │ │ 4. BACKEND RESPONSE │ │ ├── NEW USER: Creates account, returns tokens + onboarding status │ │ └── EXISTING USER: Returns tokens + current onboarding status │ │ │ │ 5. CHECK ONBOARDING STATUS │ │ └── onboarding.isComplete = false → Navigate to theonboarding client
  4. flow
  5. Client sends Firebase└── IDonboarding.isComplete token= true → Navigate to POSThome /api/v1/auth/firebase/authenticate
  6. screen
  7. Backend verifies Firebase token, creates6. userTOKEN ifMANAGEMENT new│ │ └── Store accessToken (or retrieves existing), and returns custom JWT tokens
  8. Client stores tokens securely - access token for API calls,calls) refresh token └── Store refreshToken (for renewal
  9. renewing
  10. CheckaccessToken) onboarding status in└── response to determine if user needs to complete onboarding steps

Token Refresh Flow

  1. When access tokenaccessToken expires (you receive 401 UNAUTHORIZED), call POST /api/v1/auth/refresh │ │ │ └─────────────────────────────────────────────────────────────────────────┘
  2. Backend validates refresh token and issues a new access token (same refresh token is returned)
  3. Client updates stored access token and retries the failed request

Logout Flow

  1. Client calls POST /api/v1/auth/logout with valid access token
  2. Backend revokes all refresh tokens for the user
  3. 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:05T10:30:45",
  "data": {
    // Actual response data goes here }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-01-02T10:05T10:30:45",
  "data": "Error description"
}

Standard Response Fields

FieldTypeDescription
successbooleanAlways true for successful operations, false for errors
httpStatusstringHTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.)
messagestringHuman-readable message describing the operation result
action_timestringISO 8601 timestamp of when the response was generated
dataobject/stringResponse payload for success, error details for failures

Endpoints


1. Authenticate with Firebase

Purpose: Authenticate user withExchange Firebase ID token.token for Fursa Hub access tokens. Creates new user onif first login or returns existing user.time.

Endpoint: POST {base_url}/auth/firebase/authenticate

Access Level: 🌐 Public (No authentication required)

Authentication: None (Firebase token in body)

Request Headers:

Header Type Required Description
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "firebaseToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "preferredLanguage": "en"sw",
  "theme": "DARK",
  "deviceInfo": "iPhoneAndroid 1414, Pro,Samsung iOSGalaxy 17.2"S24"
}

Request Body Parameters:

Parameter Type Required Description Validation
firebaseToken string Yes Firebase ID token obtained from Firebase Authclient SDK Must not be blankvalid Firebase token
preferredLanguage string No User's preferred language codepreference Valid2-5 codes:chars (e.g., "en", "sw", "fr")
themestringNoUI theme preferenceenum: enLIGHT, swDARK, fr, zh. Defaults to enSYSTEM
deviceInfo string No Device information for securitysession tracking Max 255 characterschars

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Authentication successful",
  "action_time": "2025-01-02T10:05T10:30:45",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tokenType": "Bearer",
    "expiresIn": 3600,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "john.doe@gmail.user@example.com",
      "username": "johndoe",
      "phoneNumber": null,
      "fullName": "John Doe",
      "profilePhotoUrl": "https://lh3.googleusercontent.com/a/photo.jpg"...",
      "isPhoneVerified": false,
      "isEmailVerified": true,
      "preferredLanguage": "en"sw",
      "theme": "DARK",
      "authProvider": "GOOGLE",
      "role": "ROLE_USER",
      "createdAt": "2025-01-02T10:05T10:30:45"
    },
    "onboarding": {
      "isComplete": false,
      "currentStep": "PENDING_PHONE_VERIFICATION"
    }
  }
}

Success Response Fields:

Field Description
accessToken JWT access token for API authenticationrequests (validexpires forin 1 hour)
refreshToken JWTToken refreshto token for obtainingget new access tokensaccessToken (validexpires forin 30 days)
tokenType Token type, alwaysAlways "Bearer"
expiresIn Access token expiration timelifetime in seconds
user.iduser UniqueUser userprofile identifier (UUID)information
user.emailtheme User's emailtheme addresspreference: fromLIGHT, FirebaseDARK, or SYSTEM
user.usernameonboarding.isComplete Auto-generatedfalse unique= usernamemust complete onboarding, true = can access app
user.phoneNumberUser's phone number (null until verified)
user.fullNameUser's display name from Firebase
user.profilePhotoUrlProfile photo URL from Firebase provider
user.isPhoneVerifiedPhone verification status
user.isEmailVerifiedEmail verification status from Firebase
user.preferredLanguageUser's language preference
user.authProviderAuthentication provider: GOOGLE, APPLE, or EMAIL
user.roleUser's role in the system
user.createdAtAccount creation timestamp
onboarding.isCompleteWhether user has completed all onboarding steps
onboarding.currentStep Current onboarding step the(see userOnboarding is ondocs)

Onboarding Status Values:

StatusDescriptionNext Action
PENDING_PHONE_VERIFICATIONUser needs to verify phone numberDirect to phone verification screen
PENDING_PREFERENCESUser needs to complete preference pagesDirect to onboarding preferences
PENDING_PROFILE_COMPLETIONUser needs to complete profileDirect to profile completion
COMPLETEDOnboarding completeDirect 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: ObtainGet a new access token using a valid refresh token.token when current one expires.

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:

HeaderTypeRequiredDescription
Content-TypestringYesMust be application/json

Request JSON Sample:

{
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Request Body Parameters:

Parameter Type Required Description Validation
refreshToken string Yes Valid refreshRefresh token obtained from authentication Must not be blankvalid, non-expired

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Token refreshed successfully",
  "action_time": "2025-01-02T11:05T11:30:45",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI1NTBl.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "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:

FieldDescription
accessTokenNew JWT access token (valid for 1 hour)
refreshTokenSame refresh token (unchanged)
tokenTypeToken type, always "Bearer"
expiresInAccess token expiration time in seconds
userCurrent user profile data
onboardingCurrent onboarding status

Error Response JSON SamplesResponses:

Invalid Refresh Token (401):

{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Invalid refresh token",
  "action_time": "2025-01-02T10:05T11:30:45",
  "data": "Invalid refresh token"
}

Expired Refresh Token (401):

{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Refresh token expired",
  "action_time": "2025-01-02T10:05T11: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 revokeInvalidate all refresh tokens for the account.user, ending all sessions.

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:05T12:00:00",
  "data": null
}

Success Response Fields:

FieldDescription
datanull (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 CurrentSupported UserLanguages

Purpose: RetrieveGet thelist currentlyof authenticatedsupported user'slanguages profilefor information.language selector screen.

Endpoint: GET {base_url}/auth/melanguages

Access Level: 🔒🌐 Protected (Requires valid access token)Public

Authentication: Bearer TokenNone

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "UserLanguages retrieved successfully",
  "action_time": "2025-01-02T10:30:45"05T10:00:00",
  "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"code": "en",
      "authProvider"name": "GOOGLE"English",
      "role"nativeName": "ROLE_USER"English"
    },
    {
      "code": "sw",
      "createdAt"name": "2025-01-02T10:30:45"Swahili",
      "nativeName": "Kiswahili"
    },
    {
      "code": "fr",
      "name": "French",
      "nativeName": "Français"
    },
    {
      "code": "zh",
      "name": "Chinese",
      "nativeName": "中文"
    }
  ]
}

Success Response Fields:

FieldDescription
idUnique user identifier (UUID)
emailUser's email address
usernameUnique username
phoneNumberVerified phone number in E.164 format (e.g., +255712345678)
fullNameUser's display name
profilePhotoUrlURL to user's profile photo
isPhoneVerifiedWhether phone number is verified
isEmailVerifiedWhether email is verified (from Firebase)
preferredLanguageUser's preferred language code
authProviderOriginal authentication provider
roleUser's role: ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN, ROLE_SUPER_ADMIN
createdAtAccount creation timestamp

Error Response JSON Samples:

Unauthorized (401):

{
  "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 CodeHTTP StatusWhen It Occurs
400BAD_REQUESTGeneral invalid request data, malformed JSON
401UNAUTHORIZEDToken empty, invalid, expired, or malformed; Firebase auth failed
403FORBIDDENAccount locked, verification required
404NOT_FOUNDRequested resource does not exist
409CONFLICTResource already exists (duplicate)
422UNPROCESSABLE_ENTITYValidation errors with detailed field information
429TOO_MANY_REQUESTSRate limit exceeded

Server-Level Exceptions (500+)

Status CodeHTTP StatusWhen It Occurs
500INTERNAL_SERVER_ERRORUnexpected server errors

Frontend Implementation Guide

TokenStep Storage1: Recommendations

First
    App
  • 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 ExpirationLaunch

1.Show Makelanguage APIselector requestscreen
with access token
2. If 401 UNAUTHORIZED received:
   a.├── Call GET /auth/refresh with refresh token
   b. If refresh successful: Update stored access token, retry original request
   c. If refresh fails (401): Clear all tokens, redirectlanguages to loginget 3.options
Continue├── withUser responseselects language
├── Store locally: selectedLanguage, theme (default: SYSTEM)
└── Navigate to sign-in screen

HandlingStep Onboarding2: StatusSign In

After

Firebase successfulSign-In
authentication,├── checkUse Firebase SDK (Google/Apple/Email)
├── On success, get Firebase ID token
└── Call POST /auth/firebase/authenticate with:
    - firebaseToken
    - preferredLanguage (from step 1)
    - theme (from step 1)
    - deviceInfo (optional)

Step 3: Handle Response

Check response.data.onboarding.currentStep:

isComplete
    ├──
  • PENDING_PHONE_VERIFICATIONfalse → Navigate to phoneonboarding verificationflow screen
  • PENDING_PREFERENCES└── Start at response.data.onboarding.currentStep └── true → Navigate to preference selection screens
  • PENDING_PROFILE_COMPLETION → Navigate to profile completionhome screen
  • COMPLETED → Navigate to main app/dashboard

Quick Reference

Authentication Header Format

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

SupportedStep Languages4: Store Tokens

Save 
securely: ├── accessToken For Authorization header ├── refreshToken For token renewal
CodeLanguage
enEnglish
swSwahili
frFrench
zhChinese

User Roles

dataFor
RoleDescription
ROLE_USERStandard└── user (default)
UI display

Step 5: API Calls

ROLE_MODERATORAll protected endpoints:
├── Add header: Authorization: Bearer {accessToken}
├── On 401 error → Try refresh token
│   ├── Success → Retry original request
│   └── Fail → Force re-login
└── Continue normal flow
Content moderator
ROLE_ADMINAdministrator
ROLE_SUPER_ADMINSuper administrator with full access