Skip to main content
POST
/
api
/
auth
/
login
Login
curl --request POST \
  --url https://api.example.com/api/auth/login \
  --header 'Content-Type: application/json' \
  --data '
{
  "username": "<string>",
  "password": "<string>"
}
'
{
  "access_token": "<string>",
  "refresh_token": "<string>",
  "token_type": "<string>",
  "user": {
    "user_id": "<string>",
    "email": "<string>",
    "role": "<string>",
    "name": "<string>",
    "is_admin": true
  },
  "expires_in": 123,
  "requires_password_change": true,
  "session": "<string>",
  "username": "<string>",
  "message": "<string>",
  "status_code": 123,
  "detail": "<string>"
}

Overview

The login endpoint authenticates users against AWS Cognito User Pool and returns JWT tokens for API access. It uses the ADMIN_USER_PASSWORD_AUTH flow with email-based authentication. Authentication Flow:
  1. Email and password are sent to AWS Cognito
  2. Cognito validates credentials
  3. Returns JWT access token and refresh token
  4. ID token is decoded to extract user information
  5. Admin status is determined based on email

Request

Body Parameters

username
string
required
User’s email address (treated as username). The API handles URL decoding and trimming automatically.Example: [email protected]
password
string
required
User’s password. Must meet Cognito password requirements:
  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • At least one special character

Request Example

{
  "username": "[email protected]",
  "password": "SecureP@ssw0rd"
}

Response

Success Response (200 OK)

access_token
string
required
JWT access token for authenticating API requests. Valid for 1 hour (3600 seconds).Format: Bearer token (JWT)
refresh_token
string
Refresh token for obtaining new access tokens without re-authentication. Valid for 30 days.Note: Only returned on initial login, not on password change required scenarios.
token_type
string
required
Always returns "bearer". Use in Authorization header as Bearer {access_token}.
user
object
required
User information extracted from Cognito ID token.
expires_in
integer
required
Token expiration time in seconds. Default is 3600 (1 hour).
requires_password_change
boolean
Present when user must change password on first login or admin-forced password reset.If true:
  • access_token will be empty
  • session field contains session token for password change
  • Client must call /api/auth/complete-password-change with session token
session
string
Session token for completing password change challenge. Only present when requires_password_change is true.
username
string
Echo of username/email. Present in password change required scenarios.
message
string
Human-readable message. Present when requires_password_change is true.

Successful Login Example

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNI...",
  "token_type": "bearer",
  "user": {
    "user_id": "b7d8f6e4-3a2c-4b9d-8e1f-5c6a7d8e9f0a",
    "email": "[email protected]",
    "role": "user",
    "name": "John Doe",
    "is_admin": false
  },
  "expires_in": 3600
}

Password Change Required Example

{
  "access_token": "",
  "token_type": "bearer",
  "user": {
    "user_id": "",
    "email": "[email protected]",
    "role": "user",
    "name": "IGAD User",
    "is_admin": false
  },
  "expires_in": 0,
  "requires_password_change": true,
  "session": "AYABeE...",
  "username": "[email protected]",
  "message": "Password change required"
}

Error Responses

status_code
integer
detail
string

401 Unauthorized - Invalid Credentials

Returned when username/password combination is incorrect.
{
  "detail": "Invalid credentials"
}
Cognito Error: NotAuthorizedException

401 Unauthorized - User Not Found

Returned when user does not exist in Cognito User Pool.
{
  "detail": "User not found"
}
Cognito Error: UserNotFoundException

500 Internal Server Error - Authentication Error

Returned for other Cognito authentication failures.
{
  "detail": "Authentication error: {error_code}"
}
Possible Cognito Errors:
  • UserNotConfirmedException - Email not verified
  • PasswordResetRequiredException - Admin-forced password reset
  • TooManyRequestsException - Rate limit exceeded

500 Internal Server Error - Generic Failure

Returned for unexpected errors.
{
  "detail": "Login failed: {error_message}"
}

Implementation Details

AWS Cognito Integration

The endpoint uses the following Cognito configuration:
  • Region: us-east-1
  • Auth Flow: ADMIN_USER_PASSWORD_AUTH
  • User Pool ID: From COGNITO_USER_POOL_ID environment variable
  • Client ID: From COGNITO_CLIENT_ID environment variable
Code Reference: backend/app/tools/auth/routes.py:39

Token Handling

Cognito returns three tokens:
  1. Access Token - Used for API authentication (1 hour expiry)
  2. ID Token - Contains user claims (decoded for user info)
  3. Refresh Token - Used to obtain new access tokens (30 days expiry)
ID Token Claims Used:
  • subuser_id
  • emailemail
  • namename

Admin Role Assignment

Admin status is determined by checking email against hardcoded list: Code Reference: backend/app/tools/auth/routes.py:97
Admin role assignment is currently hardcoded. Consider moving to Cognito User Groups for production.

Password Change Challenge

When Cognito returns ChallengeName: NEW_PASSWORD_REQUIRED:
  1. User is in FORCE_CHANGE_PASSWORD state (first login or admin reset)
  2. Response includes session token
  3. Client must call /api/auth/complete-password-change with:
    • username
    • session
    • new_password
Code Reference: backend/app/tools/auth/routes.py:67

Usage Example

cURL

curl -X POST https://api.igad.int/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "[email protected]",
    "password": "SecureP@ssw0rd"
  }'

JavaScript (Fetch)

const response = await fetch('https://api.igad.int/api/auth/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    username: '[email protected]',
    password: 'SecureP@ssw0rd',
  }),
})

const data = await response.json()

if (response.ok) {
  // Store tokens
  localStorage.setItem('accessToken', data.access_token)
  localStorage.setItem('refreshToken', data.refresh_token)
  
  // Check for password change required
  if (data.requires_password_change) {
    // Redirect to password change flow
    handlePasswordChange(data.session, data.username)
  } else {
    // Proceed to dashboard
    console.log('Logged in as:', data.user.email)
  }
} else {
  console.error('Login failed:', data.detail)
}

Python (Requests)

import requests

response = requests.post(
    'https://api.igad.int/api/auth/login',
    json={
        'username': '[email protected]',
        'password': 'SecureP@ssw0rd'
    }
)

if response.status_code == 200:
    data = response.json()
    access_token = data['access_token']
    
    # Use token for authenticated requests
    headers = {'Authorization': f'Bearer {access_token}'}
    user_response = requests.get(
        'https://api.igad.int/api/auth/me',
        headers=headers
    )
else:
    print(f"Login failed: {response.json()['detail']}")

Security Considerations

Important Security Notes:
  1. Always use HTTPS in production
  2. Never log passwords or tokens
  3. Store tokens securely (httpOnly cookies recommended over localStorage)
  4. Implement token refresh before expiry
  5. Clear tokens on logout

Token Storage

Recommended: Store in httpOnly secure cookies Alternative: localStorage with XSS protections Avoid: Regular cookies accessible to JavaScript

Rate Limiting

Cognito enforces rate limits. Handle TooManyRequestsException with exponential backoff.

Email Handling

The API handles:
  • URL-encoded emails (user%40igad.int[email protected])
  • Leading/trailing whitespace
  • Case-insensitive email matching
Code Reference: backend/app/tools/auth/routes.py:44

Build docs developers (and LLMs) love