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 identifieremail(string): User’s email addressorg(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 token403 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 use401 Unauthorized: Missing or invalid JWT token403 Forbidden: User is not a member of the organization specified in JWT404 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 set401 Unauthorized: Missing or invalid JWT token404 Not Found: User not found500 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 verificationnew_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 password404 Not Found: User not found500 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 enabledhas_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 appqr_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 token429 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:
- Display the QR code to the user
- User scans QR code with authenticator app (Google Authenticator, Authy, etc.)
- 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 enabled401 Unauthorized: Missing or invalid JWT token429 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.enabledis 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 token429 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 user401 Unauthorized: Missing or invalid JWT token429 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 name401 Unauthorized: Missing or invalid JWT token404 Not Found: Organization or service not found500 Internal Server Error: Failed to generate OAuth URL
OAuth Flow:
- Client calls this endpoint and receives
authorization_url - Client redirects user to
authorization_url - User authorizes the OAuth application
- Provider redirects back with OAuth code
- Platform completes OAuth exchange and links the identity
- User is redirected to service with query params:
- Success:
?status=success&provider={provider}&action=link - Error:
?status=error&error={message}&action=link
- Success:
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 token404 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 slugplan(string): Subscription plan name (e.g., “pro”, “enterprise”, “free”)features(array of strings): List of enabled features for this planstatus(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 providerrefresh_token(string, nullable): OAuth refresh token (if available)expires_at(string, nullable): ISO 8601 timestamp when access token expiresscopes(array of strings): List of OAuth scopes grantedprovider(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 token403 Forbidden: Service does not have scopes configured for this provider404 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