Skip to main content

Nexgate Authentication System Testing

Nexgate Authentication System - API Documentation Guide

Table of Contents

  1. Authentication Flow Overview
  2. Getting Started
  3. API Endpoints
  4. Authentication Flows
  5. Error Handling
  6. Testing Guide

Authentication Flow Overview

Nexgate uses a multi-step authentication system supporting:

  • Email/Phone OTP Authentication (passwordless)
  • Password-based Authentication
  • OAuth (Google/Apple)
  • Progressive Onboarding
  • Device Trust & Risk Assessment

Account Lifecycle States

INITIATED → OTP_VERIFIED → AGE_VERIFIED → USERNAME_SET → 
INTERESTS_SELECTED → PROFILE_COMPLETED → COMPLETED

Getting Started

Base URL

Production: https://api.nexgate.com
Development: http://localhost:8080

Common Headers

{
  "Content-Type": "application/json",
  "Authorization": "Bearer {accessToken}",
  "X-Device-Id": "unique-device-identifier",
  "X-Session-Id": "session-uuid"
}

API Endpoints

1. Start Authentication

Endpoint: POST /api/v1/auth/start

Purpose: Initialize signup or login process. System detects if user exists and their onboarding status.

Request:

{
  "identifier": "+255712345678",
  "deviceId": "device-uuid-123",
  "deviceName": "iPhone 13 Pro",
  "platform": "IOS"
}

Response - New User:

{
  "status": "success",
  "message": "Authentication initiated",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "maskedIdentifier": "••• ••• ••78",
    "provider": "PHONE",
    "otpExpiresIn": 600,
    "isNewUser": true,
    "onboardingComplete": false,
    "currentStep": "INITIATED",
    "hasPassword": false,
    "requiresOtpChannelSelection": false,
    "message": "OTP sent to ••• ••• ••78"
  }
}

Response - Existing User (Complete):

{
  "status": "success",
  "message": "Authentication initiated",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "maskedIdentifier": "user@example.com",
    "provider": "EMAIL",
    "otpExpiresIn": 600,
    "isNewUser": false,
    "onboardingComplete": true,
    "currentStep": "COMPLETED",
    "hasPassword": true,
    "message": "OTP sent to user@example.com"
  }
}

Response - Username Login (Multiple Channels):

{
  "status": "success",
  "message": "Authentication initiated",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "hasPassword": true,
    "requiresOtpChannelSelection": true,
    "availableChannels": [
      {
        "type": "EMAIL",
        "maskedValue": "k••••••@g••••.com",
        "isPrimary": true
      },
      {
        "type": "PHONE",
        "maskedValue": "••• ••• ••78",
        "isPrimary": false
      }
    ],
    "message": "Select where to receive OTP"
  }
}

Test Cases:

# New user - phone
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "+255712345678",
    "deviceId": "test-device-1",
    "deviceName": "Test iPhone",
    "platform": "IOS"
  }'

# Existing user - email
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "kibuti@example.com"
  }'

# Username login
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "kibuti_dev"
  }'

2. Send OTP to Channel

Endpoint: POST /api/v1/auth/send-otp

Purpose: Send OTP to specific channel (EMAIL or PHONE) when user has multiple contact methods.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "channel": "EMAIL"
}

Response:

{
  "status": "success",
  "message": "OTP sent",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "sentTo": "EMAIL",
    "maskedDestination": "k••••••@g••••.com",
    "expiresIn": 600,
    "message": "OTP sent to email"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/send-otp \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN",
    "channel": "EMAIL"
  }'

3. Verify OTP

Endpoint: POST /api/v1/auth/verify

Purpose: Verify OTP code. Returns different responses based on user status.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "otp": "123456",
  "deviceId": "device-uuid-123",
  "deviceName": "iPhone 13 Pro",
  "platform": "IOS"
}

Response - New User (Needs Onboarding):

{
  "status": "success",
  "message": "OTP verified",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "onboardingComplete": false,
    "currentStep": "OTP_VERIFIED",
    "nextStep": "AGE_VERIFIED",
    "message": "OTP verified. Let's set up your account."
  }
}

Response - Existing User (Complete):

{
  "status": "success",
  "message": "OTP verified",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "systemUsername": "su_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "kibuti_dev",
    "displayName": "Kibuti",
    "accountTier": "FULL",
    "onboardingComplete": true,
    "currentStep": "COMPLETED",
    "message": "Login successful"
  }
}

Response - User Resuming Onboarding:

{
  "status": "success",
  "message": "OTP verified",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "onboardingComplete": false,
    "currentStep": "USERNAME_SET",
    "nextStep": "INTERESTS_SELECTED",
    "message": "Welcome back! Continue from where you left off."
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/verify \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN",
    "otp": "123456",
    "deviceId": "test-device-1",
    "deviceName": "Test iPhone",
    "platform": "IOS"
  }'

4. Set Age (Onboarding Step 1)

Endpoint: POST /api/v1/auth/onboarding/age

Purpose: Verify user's age and determine account tier.

Request:

{
  "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "birthDate": "2000-05-15"
}

Response - Success (18+):

{
  "status": "success",
  "message": "Age verified",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "accountTier": "FULL",
    "age": 24,
    "restricted": false,
    "blocked": false,
    "currentStep": "AGE_VERIFIED",
    "nextStep": "USERNAME_SET",
    "message": "Age verified successfully"
  }
}

Response - Restricted (13-17):

{
  "status": "success",
  "message": "Age verified",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "accountTier": "RESTRICTED",
    "age": 16,
    "restricted": true,
    "blocked": false,
    "currentStep": "AGE_VERIFIED",
    "nextStep": "USERNAME_SET",
    "message": "Age verified successfully"
  }
}

Response - Blocked (Under 13):

{
  "status": "success",
  "message": "Age verified",
  "data": {
    "accountTier": null,
    "age": 11,
    "restricted": true,
    "blocked": true,
    "currentStep": "AGE_VERIFIED",
    "nextStep": null,
    "message": "You must be at least 13 years old to use NextGate"
  }
}

Test:

# Adult user
curl -X POST http://localhost:8080/api/v1/auth/onboarding/age \
  -H "Content-Type: application/json" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "birthDate": "2000-05-15"
  }'

# Minor (13-17)
curl -X POST http://localhost:8080/api/v1/auth/onboarding/age \
  -H "Content-Type: application/json" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "birthDate": "2008-05-15"
  }'

# Underage (blocked)
curl -X POST http://localhost:8080/api/v1/auth/onboarding/age \
  -H "Content-Type: application/json" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "birthDate": "2015-05-15"
  }'

5. Check Username Availability

Endpoint: POST /api/v1/auth/onboarding/check-username

Purpose: Check if username is available before setting it.

Request:

{
  "username": "kibuti_dev"
}

Response - Available:

{
  "status": "success",
  "message": "Username availability checked",
  "data": {
    "username": "kibuti_dev",
    "available": true,
    "suggestions": null
  }
}

Response - Not Available:

{
  "status": "success",
  "message": "Username availability checked",
  "data": {
    "username": "kibuti",
    "available": false,
    "suggestions": [
      "kibuti2547",
      "kibuti8912",
      "kibuti4563"
    ]
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/onboarding/check-username \
  -H "Content-Type: application/json" \
  -d '{
    "username": "kibuti_dev"
  }'

6. Set Username (Onboarding Step 2)

Endpoint: POST /api/v1/auth/onboarding/username

Purpose: Set permanent username for the account.

Request:

{
  "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "username": "kibuti_dev"
}

Response - Success:

{
  "status": "success",
  "message": "Username set",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "username": "kibuti_dev",
    "available": true,
    "currentStep": "USERNAME_SET",
    "nextStep": "INTERESTS_SELECTED",
    "message": "Username set successfully"
  }
}

Response - Not Available:

{
  "status": "success",
  "message": "Username set",
  "data": {
    "username": "kibuti",
    "available": false,
    "suggestions": [
      "kibuti2547",
      "kibuti8912",
      "kibuti4563"
    ],
    "message": "Username is not available"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/onboarding/username \
  -H "Content-Type: application/json" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "username": "kibuti_dev"
  }'

7. Set Interests (Onboarding Step 3)

Endpoint: POST /api/v1/auth/onboarding/interests

Purpose: Select user interests (minimum 3 required).

Request:

{
  "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "interestIds": [
    "550e8400-e29b-41d4-a716-446655440001",
    "550e8400-e29b-41d4-a716-446655440002",
    "550e8400-e29b-41d4-a716-446655440003"
  ]
}

Response:

{
  "status": "success",
  "message": "Interests saved",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "selectedInterests": [
      "Technology",
      "Sports",
      "Music"
    ],
    "count": 3,
    "currentStep": "INTERESTS_SELECTED",
    "nextStep": "PROFILE_COMPLETED",
    "message": "Interests saved successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/onboarding/interests \
  -H "Content-Type: application/json" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "interestIds": [
      "550e8400-e29b-41d4-a716-446655440001",
      "550e8400-e29b-41d4-a716-446655440002",
      "550e8400-e29b-41d4-a716-446655440003"
    ]
  }'

8. Complete Profile (Onboarding Step 4 - Final)

Endpoint: POST /api/v1/auth/onboarding/profile

Purpose: Complete onboarding with profile information. Returns full authentication tokens.

Request:

{
  "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "displayName": "Kibuti",
  "bio": "Software developer from Tanzania",
  "profilePictureUrl": "https://storage.nexgate.com/profile/abc123.jpg"
}

Headers:

{
  "X-Device-Id": "device-uuid-123",
  "X-Device-Name": "iPhone 13 Pro",
  "X-Platform": "IOS"
}

Response:

{
  "status": "success",
  "message": "Welcome to NextGate!",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "systemUsername": "su_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "kibuti_dev",
    "displayName": "Kibuti",
    "currentStep": "COMPLETED",
    "onboardingComplete": true,
    "message": "Welcome to NextGate!"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/onboarding/profile \
  -H "Content-Type: application/json" \
  -H "X-Device-Id: test-device-1" \
  -H "X-Device-Name: Test iPhone" \
  -H "X-Platform: IOS" \
  -d '{
    "onboardingToken": "YOUR_ONBOARDING_TOKEN",
    "displayName": "Kibuti",
    "bio": "Software developer from Tanzania",
    "profilePictureUrl": "https://example.com/photo.jpg"
  }'

9. Login with Password

Endpoint: POST /api/v1/auth/login/password

Purpose: Authenticate using password after getting tempToken from /start.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "password": "MySecurePassword123!",
  "deviceId": "device-uuid-123",
  "deviceName": "MacBook Pro",
  "platform": "WEB"
}

Response - Known Device:

{
  "status": "success",
  "message": "Login successful",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "systemUsername": "su_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "kibuti_dev",
    "displayName": "Kibuti",
    "accountTier": "FULL",
    "newDevice": false,
    "requiresDeviceVerification": false,
    "message": "Login successful"
  }
}

Response - Unknown Device (Requires Verification):

{
  "status": "success",
  "message": "Login successful",
  "data": {
    "newDevice": true,
    "requiresDeviceVerification": true,
    "deviceVerificationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "requiresChannelSelection": false,
    "otpSentTo": "••• ••• ••78",
    "message": "Device verification required. OTP sent to ••• ••• ••78"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/login/password \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN",
    "password": "MySecurePassword123!",
    "deviceId": "test-device-1",
    "deviceName": "Test MacBook",
    "platform": "WEB"
  }'

10. Send Device Verification OTP

Endpoint: POST /api/v1/auth/device/send-verification-otp

Purpose: Send OTP to specific channel for device verification when user has multiple contacts.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "channel": "EMAIL"
}

Response:

{
  "status": "success",
  "message": "Device verification OTP sent",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "sentTo": "EMAIL",
    "maskedDestination": "k••••••@g••••.com",
    "expiresIn": 600,
    "message": "OTP sent to email"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/device/send-verification-otp \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_DEVICE_VERIFICATION_TOKEN",
    "channel": "EMAIL"
  }'

11. Verify Device

Endpoint: POST /api/v1/auth/device/verify

Purpose: Verify new device with OTP and complete login.

Request:

{
  "deviceVerificationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "otp": "123456",
  "deviceId": "device-uuid-123",
  "deviceName": "MacBook Pro",
  "platform": "WEB"
}

Response:

{
  "status": "success",
  "message": "Device verified",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "systemUsername": "su_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "kibuti_dev",
    "displayName": "Kibuti",
    "accountTier": "FULL",
    "message": "Login successful"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/device/verify \
  -H "Content-Type: application/json" \
  -d '{
    "deviceVerificationToken": "YOUR_DEVICE_VERIFICATION_TOKEN",
    "otp": "123456",
    "deviceId": "test-device-1",
    "deviceName": "Test MacBook",
    "platform": "WEB"
  }'

12. OAuth Login (Google/Apple)

Endpoint: POST /api/v1/auth/login/oauth

Purpose: Authenticate using Google or Apple OAuth.

Request:

{
  "provider": "GOOGLE",
  "code": "4/0AY0e-g7xxxxxxxxxxx",
  "redirectUri": "https://app.nexgate.com/auth/callback",
  "deviceId": "device-uuid-123",
  "deviceName": "Chrome Browser",
  "platform": "WEB"
}

Response - New User:

{
  "status": "success",
  "message": "OAuth login processed",
  "data": {
    "newUser": true,
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "currentStep": "OTP_VERIFIED",
    "nextStep": "AGE_VERIFIED",
    "message": "Complete your registration"
  }
}

Response - Existing User:

{
  "status": "success",
  "message": "OAuth login processed",
  "data": {
    "newUser": false,
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "systemUsername": "su_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "kibuti_dev",
    "displayName": "Kibuti",
    "accountTier": "FULL",
    "message": "Login successful"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/login/oauth \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "GOOGLE",
    "code": "4/0AY0e-g7xxxxxxxxxxx",
    "redirectUri": "http://localhost:3000/auth/callback",
    "deviceId": "test-device-1",
    "deviceName": "Chrome Browser",
    "platform": "WEB"
  }'

13. Refresh Access Token

Endpoint: POST /api/v1/auth/token/refresh

Purpose: Get new access token using refresh token.

Request:

{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

{
  "status": "success",
  "message": "Token refreshed",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tokenType": "Bearer",
    "expiresIn": 3600,
    "message": "Token refreshed successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/token/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "YOUR_REFRESH_TOKEN"
  }'

14. Refresh Onboarding Token

Endpoint: POST /api/v1/auth/token/refresh-onboarding

Purpose: Refresh onboarding token when it expires during onboarding.

Request:

{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

{
  "status": "success",
  "message": "Onboarding token refreshed",
  "data": {
    "onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "currentStep": "USERNAME_SET",
    "nextStep": "INTERESTS_SELECTED",
    "expiresIn": 1800,
    "message": "Token refreshed successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/token/refresh-onboarding \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "YOUR_ONBOARDING_REFRESH_TOKEN"
  }'

15. Resend OTP

Endpoint: POST /api/v1/auth/otp/resend

Purpose: Resend OTP if user didn't receive it.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "alternativeDestination": null
}

Response - Success:

{
  "status": "success",
  "message": "OTP resend processed",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "maskedDestination": "••• ••• ••78",
    "sentTo": "PHONE",
    "expiresIn": 600,
    "attemptsRemaining": 4,
    "cooldownSeconds": 0,
    "message": "OTP resent to ••• ••• ••78"
  }
}

Response - Cooldown:

{
  "status": "success",
  "message": "OTP resend processed",
  "data": {
    "cooldownSeconds": 45,
    "attemptsRemaining": 3,
    "message": "Please wait 45 seconds before requesting again"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/otp/resend \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN"
  }'

16. Get OTP Status

Endpoint: GET /api/v1/auth/otp/status?tempToken={token}

Purpose: Check OTP resend cooldown and remaining attempts.

Response:

{
  "status": "success",
  "message": "OTP status",
  "data": {
    "canResend": true,
    "remainingAttempts": 4,
    "cooldownSeconds": 0
  }
}

Test:

curl -X GET "http://localhost:8080/api/v1/auth/otp/status?tempToken=YOUR_TEMP_TOKEN"

17. Initiate Password Reset

Endpoint: POST /api/v1/password/reset/initiate

Purpose: Start password reset flow.

Request:

{
  "identifier": "kibuti_dev"
}

Response - Single Channel:

{
  "status": "success",
  "message": "OTP sent",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "maskedDestination": "••• ••• ••78",
    "sentTo": "PHONE",
    "expiresIn": 600,
    "otpVerified": false,
    "passwordReset": false,
    "requiresChannelSelection": false,
    "message": "OTP sent to ••• ••• ••78"
  }
}

Response - Multiple Channels:

{
  "status": "success",
  "message": "OTP sent",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "requiresChannelSelection": true,
    "availableChannels": [
      {
        "type": "EMAIL",
        "maskedValue": "k••••••@g••••.com",
        "isPrimary": true
      },
      {
        "type": "PHONE",
        "maskedValue": "••• ••• ••78",
        "isPrimary": false
      }
    ],
    "message": "Select where to receive reset code"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/password/reset/initiate \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "kibuti_dev"
  }'

18. Send Password Reset OTP

Endpoint: POST /api/v1/password/reset/send-otp

Purpose: Send password reset OTP to specific channel.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "channel": "EMAIL"
}

Response:

{
  "status": "success",
  "message": "Reset OTP sent",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "sentTo": "EMAIL",
    "maskedDestination": "k••••••@g••••.com",
    "expiresIn": 600,
    "message": "OTP sent to email"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/password/reset/send-otp \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN",
    "channel": "EMAIL"
  }'

19. Verify Password Reset OTP

Endpoint: POST /api/v1/password/reset/verify-otp

Purpose: Verify OTP and get reset token.

Request:

{
  "tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "otp": "123456"
}

Response:

{
  "status": "success",
  "message": "OTP verified",
  "data": {
    "resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "otpVerified": true,
    "passwordReset": false,
    "message": "OTP verified. Set your new password."
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/password/reset/verify-otp \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "YOUR_TEMP_TOKEN",
    "otp": "123456"
  }'

20. Set New Password

Endpoint: POST /api/v1/password/reset/set-password

Purpose: Set new password after OTP verification.

Request:

{
  "resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "newPassword": "NewSecurePassword123!",
  "confirmPassword": "NewSecurePassword123!"
}

Response:

{
  "status": "success",
  "message": "Password reset successfully",
  "data": {
    "otpVerified": true,
    "passwordReset": true,
    "message": "Password reset successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/password/reset/set-password \
  -H "Content-Type: application/json" \
  -d '{
    "resetToken": "YOUR_RESET_TOKEN",
    "newPassword": "NewSecurePassword123!",
    "confirmPassword": "NewSecurePassword123!"
  }'

21. Change Password (Authenticated)

Endpoint: POST /api/v1/password/change

Purpose: Change password for logged-in user.

Request:

{
  "currentPassword": "OldPassword123!",
  "newPassword": "NewPassword123!",
  "confirmPassword": "NewPassword123!"
}

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

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

Test:

curl -X POST http://localhost:8080/api/v1/password/change \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "currentPassword": "OldPassword123!",
    "newPassword": "NewPassword123!",
    "confirmPassword": "NewPassword123!"
  }'

22. Set Password (Passwordless Users)

Endpoint: POST /api/v1/password/set

Purpose: Allow passwordless users to set a password.

Request:

{
  "password": "MyFirstPassword123!",
  "confirmPassword": "MyFirstPassword123!"
}

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

{
  "status": "success",
  "message": "Password set successfully",
  "data": {
    "success": true,
    "hadPassword": false,
    "message": "Password set successfully. You can now login with email/phone and password."
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/password/set \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "password": "MyFirstPassword123!",
    "confirmPassword": "MyFirstPassword123!"
  }'

23. Check Can Set Password

Endpoint: GET /api/v1/password/can-set

Purpose: Check if user is eligible to set a password.

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

{
  "status": "success",
  "message": "You can set a password",
  "data": {
    "canSetPassword": true,
    "OAuthProvider": "EMAIL"
  }
}

Test:

curl -X GET http://localhost:8080/api/v1/password/can-set \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

24. Get User Sessions

Endpoint: GET /api/v1/auth/sessions

Purpose: List all active sessions for the user.

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "X-Session-Id": "current-session-uuid"
}

Response:

{
  "status": "success",
  "message": "Sessions retrieved",
  "data": {
    "sessions": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "deviceId": "device-uuid-123",
        "deviceName": "iPhone 13 Pro",
        "platform": "IOS",
        "ipAddress": "192.168.1.100",
        "location": "Dar es Salaam, Tanzania",
        "lastActiveAt": "2026-01-20T10:30:00Z",
        "createdAt": "2026-01-15T08:00:00Z",
        "currentSession": true
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "deviceId": "device-uuid-456",
        "deviceName": "MacBook Pro",
        "platform": "WEB",
        "ipAddress": "192.168.1.101",
        "location": "Mbeya, Tanzania",
        "lastActiveAt": "2026-01-19T14:20:00Z",
        "createdAt": "2026-01-10T12:00:00Z",
        "currentSession": false
      }
    ],
    "totalCount": 2,
    "currentSession": {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "deviceId": "device-uuid-123",
      "deviceName": "iPhone 13 Pro",
      "platform": "IOS",
      "ipAddress": "192.168.1.100",
      "location": "Dar es Salaam, Tanzania",
      "lastActiveAt": "2026-01-20T10:30:00Z",
      "createdAt": "2026-01-15T08:00:00Z",
      "currentSession": true
    }
  }
}

Test:

curl -X GET http://localhost:8080/api/v1/auth/sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Session-Id: YOUR_SESSION_ID"

25. Sign Out Current Session

Endpoint: POST /api/v1/auth/sessions/sign-out

Purpose: Sign out from current session.

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "X-Session-Id": "current-session-uuid"
}

Response:

{
  "status": "success",
  "message": "Signed out successfully",
  "data": {
    "success": true,
    "sessionsRevoked": 1,
    "currentSessionRevoked": true,
    "message": "Signed out successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/sessions/sign-out \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Session-Id: YOUR_SESSION_ID"

26. Sign Out Other Sessions

Endpoint: POST /api/v1/auth/sessions/sign-out-others

Purpose: Sign out all sessions except current one.

Request:

{
  "password": "MyPassword123!",
  "otp": null
}

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "X-Session-Id": "current-session-uuid"
}

Response:

{
  "status": "success",
  "message": "Signed out of other devices",
  "data": {
    "success": true,
    "sessionsRevoked": 3,
    "currentSessionRevoked": false,
    "message": "Signed out of 3 other device(s)"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/sessions/sign-out-others \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Session-Id: YOUR_SESSION_ID" \
  -d '{
    "password": "MyPassword123!"
  }'

27. Sign Out All Sessions

Endpoint: POST /api/v1/auth/sessions/sign-out-all

Purpose: Sign out from all sessions including current.

Request:

{
  "password": "MyPassword123!",
  "confirm": true
}

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

{
  "status": "success",
  "message": "Signed out of all devices",
  "data": {
    "success": true,
    "sessionsRevoked": 4,
    "currentSessionRevoked": true,
    "message": "Signed out of all devices"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/sessions/sign-out-all \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "password": "MyPassword123!",
    "confirm": true
  }'

28. Get Registered Devices

Endpoint: GET /api/v1/auth/devices

Purpose: List all registered trusted devices.

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "X-Device-Id": "current-device-uuid"
}

Response:

{
  "status": "success",
  "message": "Devices retrieved",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "deviceId": "device-uuid-123",
      "deviceName": "iPhone 13 Pro",
      "platform": "IOS",
      "trustLevel": "HIGH",
      "location": "Dar es Salaam, Tanzania",
      "lastUsedAt": "2026-01-20T10:30:00Z",
      "createdAt": "2026-01-15T08:00:00Z",
      "currentDevice": true
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440002",
      "deviceId": "device-uuid-456",
      "deviceName": "MacBook Pro",
      "platform": "WEB",
      "trustLevel": "MEDIUM",
      "location": "Mbeya, Tanzania",
      "lastUsedAt": "2026-01-19T14:20:00Z",
      "createdAt": "2026-01-10T12:00:00Z",
      "currentDevice": false
    }
  ]
}

Test:

curl -X GET http://localhost:8080/api/v1/auth/devices \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Device-Id: YOUR_DEVICE_ID"

29. Register Device

Endpoint: POST /api/v1/auth/devices/register

Purpose: Manually register a new trusted device.

Request:

{
  "deviceId": "device-uuid-789",
  "deviceName": "iPad Pro",
  "platform": "IOS",
  "deviceModel": "iPad Pro 12.9",
  "osVersion": "iOS 17.2"
}

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

{
  "status": "success",
  "message": "Device registered",
  "data": {
    "deviceKeyId": "550e8400-e29b-41d4-a716-446655440003",
    "deviceId": "device-uuid-789",
    "deviceName": "iPad Pro",
    "platform": "IOS",
    "trustLevel": "HIGH",
    "message": "Device registered successfully"
  }
}

Test:

curl -X POST http://localhost:8080/api/v1/auth/devices/register \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "deviceId": "device-uuid-789",
    "deviceName": "iPad Pro",
    "platform": "IOS"
  }'

30. Revoke Device

Endpoint: DELETE /api/v1/auth/devices/{deviceKeyId}

Purpose: Remove a trusted device.

Headers:

{
  "Authorization": "Bearer YOUR_ACCESS_TOKEN"
}

Response:

{
  "status": "success",
  "message": "Device revoked"
}

Test:

curl -X DELETE http://localhost:8080/api/v1/auth/devices/550e8400-e29b-41d4-a716-446655440003 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Error Handling

Common Error Responses

400 Bad Request - Validation Error:

{
  "status": "error",
  "message": "Validation failed",
  "errors": {
    "username": "Username must be 3-30 characters",
    "password": "Password must be at least 8 characters"
  }
}

401 Unauthorized:

{
  "status": "error",
  "message": "Invalid or expired token"
}

403 Forbidden:

{
  "status": "error",
  "message": "Account is locked"
}

404 Not Found:

{
  "status": "error",
  "message": "Account not found"
}

429 Too Many Requests:

{
  "status": "error",
  "message": "Too many OTP requests. Please wait before requesting again."
}

500 Internal Server Error:

{
  "status": "error",
  "message": "An unexpected error occurred"
}

Complete Authentication Flows

Flow A: New User Registration (Phone + Password)

# Step 1: Start authentication
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{"identifier": "+255712345678", "deviceId": "device-1"}'

# Step 2: Verify OTP
curl -X POST http://localhost:8080/api/v1/auth/verify \
  -H "Content-Type: application/json" \
  -d '{"tempToken": "TOKEN_FROM_STEP_1", "otp": "123456", "deviceId": "device-1"}'

# Step 3: Set age
curl -X POST http://localhost:8080/api/v1/auth/onboarding/age \
  -H "Content-Type: application/json" \
  -d '{"onboardingToken": "TOKEN_FROM_STEP_2", "birthDate": "2000-05-15"}'

# Step 4: Set username
curl -X POST http://localhost:8080/api/v1/auth/onboarding/username \
  -H "Content-Type: application/json" \
  -d '{"onboardingToken": "TOKEN_FROM_STEP_3", "username": "kibuti_dev"}'

# Step 5: Set interests
curl -X POST http://localhost:8080/api/v1/auth/onboarding/interests \
  -H "Content-Type: application/json" \
  -d '{"onboardingToken": "TOKEN_FROM_STEP_4", "interestIds": ["uuid1", "uuid2", "uuid3"]}'

# Step 6: Complete profile
curl -X POST http://localhost:8080/api/v1/auth/onboarding/profile \
  -H "Content-Type: application/json" \
  -H "X-Device-Id: device-1" \
  -d '{"onboardingToken": "TOKEN_FROM_STEP_5", "displayName": "Kibuti"}'

# Step 7: Set password (optional)
curl -X POST http://localhost:8080/api/v1/password/set \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN_FROM_STEP_6" \
  -d '{"password": "MyPassword123!", "confirmPassword": "MyPassword123!"}'

Flow B: Existing User Login (Password)

# Step 1: Start authentication
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{"identifier": "kibuti_dev", "deviceId": "device-2"}'

# Step 2: Login with password
curl -X POST http://localhost:8080/api/v1/auth/login/password \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "TOKEN_FROM_STEP_1",
    "password": "MyPassword123!",
    "deviceId": "device-2",
    "deviceName": "MacBook Pro",
    "platform": "WEB"
  }'

# If unknown device, Step 3: Verify device
curl -X POST http://localhost:8080/api/v1/auth/device/verify \
  -H "Content-Type: application/json" \
  -d '{
    "deviceVerificationToken": "TOKEN_FROM_STEP_2",
    "otp": "123456",
    "deviceId": "device-2",
    "deviceName": "MacBook Pro",
    "platform": "WEB"
  }'

Flow C: Passwordless Login

# Step 1: Start authentication
curl -X POST http://localhost:8080/api/v1/auth/start \
  -H "Content-Type: application/json" \
  -d '{"identifier": "+255712345678", "deviceId": "device-3"}'

# Step 2: Verify OTP (logs in directly)
curl -X POST http://localhost:8080/api/v1/auth/verify \
  -H "Content-Type: application/json" \
  -d '{
    "tempToken": "TOKEN_FROM_STEP_1",
    "otp": "123456",
    "deviceId": "device-3",
    "deviceName": "Android Phone",
    "platform": "ANDROID"
  }'

Testing Checklist

Registration Flow

  • New user can register with phone
  • New user can register with email
  • OTP is sent successfully
  • OTP verification works
  • Age verification blocks users under 13
  • Age verification sets RESTRICTED tier for 13-17
  • Username validation works
  • Username suggestions provided for taken usernames
  • Interest selection requires minimum 3
  • Profile completion returns access tokens
  • Device is registered automatically

Login Flow

  • Can login with username
  • Can login with email
  • Can login with phone
  • Password login works for known devices
  • Unknown devices require OTP verification
  • Device verification OTP works
  • Passwordless login works
  • Risk assessment triggers on suspicious login
  • Failed attempts are rate limited

OAuth Flow

  • Google OAuth works for new users
  • Google OAuth works for existing users
  • Apple OAuth works (if configured)
  • OAuth email is auto-verified
  • Profile data is enriched from OAuth

Token Management

  • Access token expires in 1 hour
  • Refresh token works
  • Onboarding token refresh works
  • Expired tokens are rejected
  • Revoked tokens don't work

Password Management

  • Password reset sends OTP
  • Password reset OTP verification works
  • New password can be set
  • Password change requires current password
  • Passwordless users can set password
  • All sessions revoked after password reset

Session Management

  • Can view all active sessions
  • Can sign out current session
  • Can sign out other sessions
  • Can sign out all sessions
  • Session requires password/OTP confirmation

Device Management

  • Can view registered devices
  • Can manually register device
  • Can revoke device
  • Trust levels are assigned correctly

End of Documentation

This guide provides complete endpoint documentation with request/response examples and curl commands for testing each endpoint. Save this as AUTHENTICATION_API_GUIDE.md in your project documentation.