User Management API

Complete API documentation for user-related endpoints including profile management, password changes, MFA setup, backup codes, and OAuth identity linking.

Updated Nov 15, 2025
Edit on GitHub
user-management profile mfa password identities

User Management API Reference

Overview

The User Management API provides comprehensive endpoints for managing authenticated user profiles, multi-factor authentication (MFA), password management, subscription information, linked social identities, and provider token access.

All endpoints in this reference require authentication via JWT token (passed in the Authorization: Bearer {token} header) unless otherwise noted.

Endpoints Summary

Endpoint Method Description
/api/user GET Get authenticated user profile
/api/user PATCH Update user profile
/api/user/set-password POST Set password for OAuth-only accounts
/api/user/change-password POST Change user password
/api/user/mfa/status GET Check MFA status
/api/user/mfa/setup POST Initiate TOTP MFA setup
/api/user/mfa/verify POST Verify TOTP code and enable MFA
/api/user/mfa DELETE Disable MFA
/api/user/mfa/backup-codes/regenerate POST Regenerate MFA backup codes
/api/user/identities GET List linked social accounts
/api/user/identities/:provider/link POST Start linking social account
/api/user/identities/:provider DELETE Unlink social account
/api/subscription GET Get user subscription details
/api/provider-token/:provider GET Get OAuth access token for provider

Profile Management

GET /api/user

Get the profile of the currently authenticated user.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "org": "acme-corp",
  "service": "main-app"
}

Response Fields:

  • id (string): User’s unique identifier
  • email (string): User’s email address
  • org (string): Organization slug from JWT (empty string if not in org context)
  • service (string): Service slug from JWT (empty string if not in service context)

Example Request:

curl -X GET https://sso.example.com/api/user \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not a member of the organization specified in JWT

PATCH /api/user

Update the authenticated user’s profile information.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user (can only update their own profile)

Request Body:

{
  "email": "newemail@example.com"
}

Request Fields:

  • email (string, optional): New email address (must be valid format and not already taken)

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "newemail@example.com",
  "org": "acme-corp",
  "service": "main-app"
}

Example Request:

curl -X PATCH https://sso.example.com/api/user \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newemail@example.com"
  }'

Error Responses:

  • 400 Bad Request: Invalid email format or email already in use
  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not a member of the organization specified in JWT
  • 404 Not Found: User not found

Password Management

POST /api/user/set-password

Set a password for OAuth-only users. This endpoint allows users who authenticated via OAuth (GitHub, Google, Microsoft) to add password-based authentication to their account.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Note: This endpoint only works if the user does not already have a password set. If a password exists, use the change-password endpoint instead.

Request Body:

{
  "new_password": "securepassword123"
}

Request Fields:

  • new_password (string, required): New password (minimum 8 characters)

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Response (200 OK):

{
  "message": "Password set successfully"
}

Example Request:

curl -X POST https://sso.example.com/api/user/set-password \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "new_password": "securepassword123"
  }'

Error Responses:

  • 400 Bad Request: Password too short (less than 8 characters) or password already set
  • 401 Unauthorized: Missing or invalid JWT token
  • 404 Not Found: User not found
  • 500 Internal Server Error: Failed to hash password

POST /api/user/change-password

Change the authenticated user’s password. Requires the current password for verification. All other sessions will be revoked for security after a successful password change.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Note: This endpoint only works if the user already has a password set. OAuth-only users should use set-password first.

Request Body:

{
  "current_password": "oldpassword",
  "new_password": "newpassword123"
}

Request Fields:

  • current_password (string, required): Current password for verification
  • new_password (string, required): New password (minimum 8 characters)

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Response (200 OK):

{
  "message": "Password changed successfully"
}

Example Request:

curl -X POST https://sso.example.com/api/user/change-password \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "current_password": "oldpassword",
    "new_password": "newpassword123"
  }'

Error Responses:

  • 400 Bad Request: New password too short (less than 8 characters) or user has no password set (OAuth-only account)
  • 401 Unauthorized: Missing/invalid JWT token or incorrect current password
  • 404 Not Found: User not found
  • 500 Internal Server Error: Failed to hash password

Security Notes:

  • Current password must be correct to proceed
  • All other user sessions are automatically revoked after password change
  • Password is hashed using Argon2 before storage

Multi-Factor Authentication (MFA)

GET /api/user/mfa/status

Check if multi-factor authentication (TOTP) is enabled for the authenticated user and whether backup codes are available.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "enabled": true,
  "has_backup_codes": true
}

Response Fields:

  • enabled (boolean): Whether MFA is currently enabled
  • has_backup_codes (boolean): Whether unused backup codes are available

Example Request:

curl -X GET https://sso.example.com/api/user/mfa/status \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token

POST /api/user/mfa/setup

Initiate TOTP (Time-based One-Time Password) MFA setup. This endpoint generates a new TOTP secret and QR code for the user to scan with their authenticator app (Google Authenticator, Authy, etc.).

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Rate Limiting: 5 requests per 5 minutes per IP address

Request Body: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "secret": "JBSWY3DPEHPK3PXP",
  "qr_code_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\"...",
  "qr_code_uri": "otpauth://totp/SSO:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=SSO"
}

Response Fields:

  • secret (string): Base32-encoded TOTP secret (for manual entry)
  • qr_code_svg (string): SVG QR code for scanning with authenticator app
  • qr_code_uri (string): TOTP URI format (otpauth://)

Example Request:

curl -X POST https://sso.example.com/api/user/mfa/setup \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 429 Too Many Requests: Rate limit exceeded (5 requests per 5 minutes)
  • 500 Internal Server Error: Failed to generate TOTP secret or QR code

Next Steps:

  1. Display the QR code to the user
  2. User scans QR code with authenticator app (Google Authenticator, Authy, etc.)
  3. User calls /api/user/mfa/verify with a code from their app to complete setup

Security Notes:

  • The TOTP secret is encrypted before storage using AES-GCM
  • If a setup was previously initiated but not completed, this endpoint will replace the old secret
  • MFA is not enabled until verified via /api/user/mfa/verify

POST /api/user/mfa/verify

Verify a TOTP code and enable MFA for the authenticated user. This endpoint must be called after /api/user/mfa/setup to complete MFA setup.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Rate Limiting: 5 requests per 5 minutes per IP address

Request Body:

{
  "code": "123456"
}

Request Fields:

  • code (string, required): 6-digit TOTP code from authenticator app

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Response (200 OK):

{
  "enabled": true,
  "backup_codes": [
    "A1B2-C3D4",
    "E5F6-G7H8",
    "I9J0-K1L2",
    "M3N4-O5P6",
    "Q7R8-S9T0",
    "U1V2-W3X4",
    "Y5Z6-A7B8",
    "C9D0-E1F2",
    "G3H4-I5J6",
    "K7L8-M9N0"
  ]
}

Response Fields:

  • enabled (boolean): Confirms MFA is now enabled (always true on success)
  • backup_codes (array of strings): 10 backup codes for account recovery (formatted as XXXX-XXXX)

Example Request:

curl -X POST https://sso.example.com/api/user/mfa/verify \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "code": "123456"
  }'

Error Responses:

  • 400 Bad Request: Invalid TOTP code, MFA not set up, or MFA already enabled
  • 401 Unauthorized: Missing or invalid JWT token
  • 429 Too Many Requests: Rate limit exceeded (5 requests per 5 minutes)
  • 500 Internal Server Error: Failed to hash backup codes or database error

Important Notes:

  • SAVE THE BACKUP CODES: Backup codes are only shown once. Store them securely for account recovery.
  • Each backup code can be used only once
  • After successful verification, MFA will be required for all future logins
  • A webhook event user.mfa.enabled is published to configured webhooks

DELETE /api/user/mfa

Disable TOTP multi-factor authentication for the authenticated user. This removes all TOTP secrets and backup codes.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Rate Limiting: 5 requests per 5 minutes per IP address

Request Body: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "success": true,
  "message": "MFA has been disabled"
}

Example Request:

curl -X DELETE https://sso.example.com/api/user/mfa \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 429 Too Many Requests: Rate limit exceeded (5 requests per 5 minutes)
  • 500 Internal Server Error: Database error

Security Notes:

  • This action immediately disables MFA
  • All TOTP secrets and backup codes are permanently deleted
  • User will no longer be prompted for MFA codes during login
  • An audit log entry is created

POST /api/user/mfa/backup-codes/regenerate

Regenerate MFA backup codes. This replaces all existing backup codes with new ones. Useful if backup codes are lost, compromised, or all used up.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user with MFA enabled

Rate Limiting: 5 requests per 5 minutes per IP address

Request Body: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "backup_codes": [
    "X1Y2-Z3A4",
    "B5C6-D7E8",
    "F9G0-H1I2",
    "J3K4-L5M6",
    "N7O8-P9Q0",
    "R1S2-T3U4",
    "V5W6-X7Y8",
    "Z9A0-B1C2",
    "D3E4-F5G6",
    "H7I8-J9K0"
  ]
}

Response Fields:

  • backup_codes (array of strings): 10 new backup codes (formatted as XXXX-XXXX)

Example Request:

curl -X POST https://sso.example.com/api/user/mfa/backup-codes/regenerate \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 400 Bad Request: MFA is not enabled for this user
  • 401 Unauthorized: Missing or invalid JWT token
  • 429 Too Many Requests: Rate limit exceeded (5 requests per 5 minutes)
  • 500 Internal Server Error: Failed to generate or hash backup codes

Important Notes:

  • SAVE THE NEW BACKUP CODES: Store them securely as they replace all previous codes
  • All previously generated backup codes (used and unused) are invalidated
  • Each backup code can be used only once
  • An audit log entry is created

Identity Management

GET /api/user/identities

List all social accounts (OAuth identities) linked to the authenticated user in the current authentication context.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Context: Identities are scoped to the authentication context (platform-level or service-level). Service-level JWTs will only see identities linked in that service’s context.

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

[
  {
    "provider": "github"
  },
  {
    "provider": "google"
  },
  {
    "provider": "microsoft"
  }
]

Response Fields:

  • Array of identity objects, each containing:
    • provider (string): OAuth provider name (github, google, or microsoft)

Example Request:

curl -X GET https://sso.example.com/api/user/identities \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token

Context Isolation:

  • Platform-level JWT (no org/service): Returns all platform-level identities
  • Service-level JWT (with org/service): Returns only identities linked in that service context

POST /api/user/identities/:provider/link

Start the OAuth flow to link a new social account to the authenticated user. Returns an authorization URL that the user should be redirected to.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Path Parameters:

  • provider (string): OAuth provider to link (github, google, or microsoft)

Query Parameters: None

Request Body: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "authorization_url": "https://github.com/login/oauth/authorize?client_id=...&state=...&redirect_uri=..."
}

Response Fields:

  • authorization_url (string): OAuth authorization URL to redirect user to

Example Request:

curl -X POST https://sso.example.com/api/user/identities/github/link \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 400 Bad Request: Invalid provider name
  • 401 Unauthorized: Missing or invalid JWT token
  • 404 Not Found: Organization or service not found
  • 500 Internal Server Error: Failed to generate OAuth URL

OAuth Flow:

  1. Client calls this endpoint and receives authorization_url
  2. Client redirects user to authorization_url
  3. User authorizes the OAuth application
  4. Provider redirects back with OAuth code
  5. Platform completes OAuth exchange and links the identity
  6. User is redirected to service with query params:
    • Success: ?status=success&provider={provider}&action=link
    • Error: ?status=error&error={message}&action=link

Context Behavior:

  • Platform context: Uses platform OAuth credentials and default scopes
  • Service context: Uses service-configured scopes and organization’s BYOO credentials (if configured)

DELETE /api/user/identities/:provider

Unlink a social account from the authenticated user. This removes the OAuth identity and revokes access tokens.

Authentication: Required (any valid JWT)

Permissions: Any authenticated user

Path Parameters:

  • provider (string): OAuth provider to unlink (github, google, or microsoft)

Query Parameters: None

Request Body: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (204 No Content): Empty response body

Example Request:

curl -X DELETE https://sso.example.com/api/user/identities/github \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 400 Bad Request: Invalid provider name or cannot unlink last identity (account lockout prevention)
  • 401 Unauthorized: Missing or invalid JWT token
  • 404 Not Found: Identity for provider not found

Security Notes:

  • Cannot unlink the last identity (prevents account lockout)
  • At least one authentication method must remain available
  • Deletion is scoped to the current authentication context (platform or service)

Subscription Management

GET /api/subscription

Get the authenticated user’s subscription details for the service specified in the JWT token.

Authentication: Required (service context JWT)

Permissions: Any authenticated user with service context JWT

Note: The JWT must contain both org and service claims. This endpoint cannot be called with platform-level JWTs.

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "service": "main-app",
  "plan": "pro",
  "features": ["advanced_analytics", "api_access", "priority_support"],
  "status": "active",
  "current_period_end": "2025-02-15T10:30:00Z"
}

Response Fields:

  • service (string): Service slug
  • plan (string): Subscription plan name (e.g., “pro”, “enterprise”, “free”)
  • features (array of strings): List of enabled features for this plan
  • status (string): Subscription status (active, cancelled, past_due, etc.)
  • current_period_end (string): ISO 8601 timestamp when current billing period ends

Example Request:

curl -X GET https://sso.example.com/api/subscription \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 400 Bad Request: Missing org or service in JWT token (not a service context JWT)
  • 401 Unauthorized: Missing or invalid JWT token

Free Plan Response: If the user has no active subscription, a default free plan is returned:

{
  "service": "main-app",
  "plan": "free",
  "features": [],
  "status": "active",
  "current_period_end": "N/A"
}

Provider Token Access

GET /api/provider-token/:provider

Retrieve a fresh, valid OAuth access token for an external provider on behalf of the authenticated user. This endpoint automatically refreshes expired tokens and returns valid credentials.

Authentication: Required (service context JWT only)

Permissions: User must have authenticated with the provider through this specific service

Path Parameters:

  • provider (string): OAuth provider name (github, google, or microsoft)

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "refresh_token": "ghr_1B4a2e6922B7132F62130B32397Cd54d",
  "expires_at": "2025-01-15T12:30:00Z",
  "scopes": ["user:email", "read:org"],
  "provider": "github"
}

Response Fields:

  • access_token (string): Valid OAuth access token for the provider
  • refresh_token (string, nullable): OAuth refresh token (if available)
  • expires_at (string, nullable): ISO 8601 timestamp when access token expires
  • scopes (array of strings): List of OAuth scopes granted
  • provider (string): Provider name

Example Request:

curl -X GET https://sso.example.com/api/provider-token/github \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Error Responses:

  • 400 Bad Request: Provider tokens can only be requested in service context (JWT must have service claim)
  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: Service does not have scopes configured for this provider
  • 404 Not Found: Service not found or user has not authenticated with this provider for this service

Token Refresh Behavior:

  • If token expires within 5 minutes, it is automatically refreshed before returning
  • GitHub tokens do not support refresh (long-lived tokens)
  • Microsoft and Google tokens are refreshed using the stored refresh token
  • Token refresh is protected by a distributed lock to prevent concurrent refreshes

Service-Level Isolation:

  • Tokens are scoped to the specific service that issued them
  • Each service has its own set of user tokens (even within the same organization)
  • This ensures proper OAuth credential isolation when using BYOO (Bring Your Own OAuth)

Use Cases:

  • Making API calls to GitHub/Google/Microsoft on behalf of the user
  • Accessing user data from the provider (emails, repositories, calendar, etc.)
  • Maintaining seamless integration with external services

Security Notes:

  • Access tokens are encrypted at rest using AES-GCM
  • Tokens are decrypted on-the-fly when requested
  • Refresh tokens are rotated when used (where supported by provider)
  • All token operations are logged for audit purposes

Error Responses

All endpoints may return the following standard error responses:

401 Unauthorized

{
  "error": "Not authenticated",
  "error_code": "UNAUTHORIZED",
  "timestamp": "2025-01-15T10:30:00Z"
}

403 Forbidden

{
  "error": "Insufficient permissions",
  "error_code": "FORBIDDEN",
  "timestamp": "2025-01-15T10:30:00Z"
}

404 Not Found

{
  "error": "Resource not found",
  "error_code": "NOT_FOUND",
  "timestamp": "2025-01-15T10:30:00Z"
}

400 Bad Request

{
  "error": "Invalid request parameters",
  "error_code": "BAD_REQUEST",
  "timestamp": "2025-01-15T10:30:00Z"
}

429 Too Many Requests

{
  "error": "Rate limit exceeded",
  "error_code": "RATE_LIMIT_EXCEEDED",
  "timestamp": "2025-01-15T10:30:00Z"
}

500 Internal Server Error

{
  "error": "An unexpected error occurred",
  "error_code": "INTERNAL_SERVER_ERROR",
  "timestamp": "2025-01-15T10:30:00Z"
}

Rate Limiting

MFA-related endpoints have specialized rate limiting to prevent brute-force attacks:

  • MFA Setup Endpoints (/api/user/mfa/setup, /api/user/mfa/verify, /api/user/mfa, /api/user/mfa/backup-codes/regenerate):

    • Limit: 5 requests per 5 minutes per IP address
    • Burst size: 5 requests
  • MFA Verification Endpoint (/api/auth/mfa/verify):

    • Limit: 3 requests per minute per IP address
    • Burst size: 3 requests

Rate limit headers are included in responses:

X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1642244400

Authentication Context

JWT tokens carry authentication context that affects endpoint behavior:

Platform Context JWT

{
  "sub": "user-id",
  "email": "user@example.com",
  "is_platform_owner": false,
  "exp": 1642244400,
  "iat": 1642158000
}
  • No organization or service context
  • Used for platform-level operations
  • Identity operations affect platform-level identities

Organization Context JWT

{
  "sub": "user-id",
  "email": "user@example.com",
  "org": "acme-corp",
  "is_platform_owner": false,
  "exp": 1642244400,
  "iat": 1642158000
}
  • Organization context, no specific service
  • Used for organization management operations

Service Context JWT

{
  "sub": "user-id",
  "email": "user@example.com",
  "org": "acme-corp",
  "service": "main-app",
  "plan": "pro",
  "features": ["advanced_analytics"],
  "exp": 1642244400,
  "iat": 1642158000
}
  • Full service context with organization
  • Required for subscription and provider-token endpoints
  • Identity operations affect service-level identities
  • Enables BYOO (Bring Your Own OAuth) credential isolation