Login and Authentication Flows
The IAM service supports multiple authentication flows to accommodate different client types and enterprise requirements. This section covers credential-based login, OAuth2 grant types, SSO federation, risk-based adaptive authentication, and account lockout behavior.
Credential Login Flow
The primary login flow authenticates users with email and password, optionally gating on MFA if enabled:
Client IAM Service Database
| | |
| POST /api/v1/auth/login | |
|------------------------->| |
| | Lookup user by email |
| |------------------------------>|
| | Verify password (BCrypt) |
| | Check account status |
| | Run risk assessment |
| | |
| |--- If MFA enabled ---------->|
| | Create MFA challenge |
| MfaChallengeResponse | Return challenge ID |
|<-------------------------| |
| | |
| |--- If MFA not enabled ------>|
| | Generate access token |
| | Generate refresh token |
| | Create session |
| AuthResponse | Record login history |
|<-------------------------|------------------------------>|Login Request
POST /api/v1/auth/login
Content-Type: application/json{
"email": "jane.doe@acme.com",
"password": "SecureP@ssw0rd!"
}Response: Login Success (No MFA)
When the user does not have MFA enabled, the response contains tokens immediately:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "Bearer",
"expiresIn": 900,
"user": {
"id": 42,
"email": "jane.doe@acme.com",
"firstName": "Jane",
"lastName": "Doe",
"emailVerified": true,
"mfaEnabled": false,
"roles": ["ADMIN", "ANALYST"]
}
}Response: MFA Required
When the user has MFA enabled, the response contains a challenge instead of tokens:
{
"mfaRequired": true,
"challengeId": "chg_a1b2c3d4e5f6",
"mfaMethods": ["TOTP", "SMS", "BACKUP_CODE"],
"expiresIn": 300
}The client must then call POST /api/v1/auth/mfa/verify to complete authentication.
Response: Errors
| HTTP Status | Condition | Response Body |
|---|---|---|
| 401 | Invalid credentials | { "code": "AUTHENTICATION_FAILED", "message": "Invalid email or password" } |
| 423 | Account locked | { "code": "ACCOUNT_LOCKED", "message": "Account locked due to too many failed attempts", "retryAfter": 1800 } |
| 403 | Account disabled | { "code": "ACCOUNT_DISABLED", "message": "Account has been deactivated" } |
MFA Verification During Login
When a login returns an MFA challenge, the client submits the verification code:
POST /api/v1/auth/mfa/verify
Content-Type: application/json{
"challengeId": "chg_a1b2c3d4e5f6",
"code": "482916",
"method": "TOTP"
}The method field indicates which MFA factor the user is verifying. Supported values:
| Method | Code Source |
|---|---|
TOTP | Authenticator app (Google Authenticator, Authy, 1Password) |
SMS | SMS message sent to registered phone number |
EMAIL | Email sent to registered email address |
BACKUP_CODE | One-time backup code from initial enrollment |
On successful verification, the response is identical to a direct login success (AuthResponse with tokens).
OAuth2 Authorization Code Grant
The IAM service implements the OAuth2 authorization code flow with PKCE (RFC 7636) for third-party application integration.
Step 1: Authorization Request
GET /api/v1/oauth2/authorize
?response_type=code
&client_id=my-app-client-id
&redirect_uri=https://myapp.com/callback
&scope=read write
&state=random-csrf-token
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256The authorization controller validates the request, authenticates the user (redirecting to login if needed), and returns an authorization code to the redirect URI.
Step 2: Token Exchange
POST /api/v1/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=auth_code_value
&redirect_uri=https://myapp.com/callback
&client_id=my-app-client-id
&client_secret=my-app-client-secret
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkResponse:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"scope": "read write"
}The OAuth2 access token includes additional claims compared to the standard access token:
| Claim | Description |
|---|---|
client_id | The OAuth2 client that requested the token |
scope | Space-separated list of granted scopes |
user_id | The authenticated user's ID |
tenant_id | The tenant context |
token_type | Always "access_token" |
OAuth2 Client Credentials Grant
For service-to-service authentication where no user context is needed:
POST /api/v1/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=my-service-client-id
&client_secret=my-service-client-secret
&scope=api:read api:writeResponse:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api:read api:write"
}Client credentials tokens have grant_type: "client_credentials" in their claims and use the client ID as the JWT subject (instead of a user email).
OAuth2 Refresh Token Grant
Tokens can be refreshed using a valid refresh token:
POST /api/v1/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=eyJhbGciOiJIUzI1NiIs...
&client_id=my-app-client-id
&client_secret=my-app-client-secretOAuth2 Token Revocation
Active tokens can be revoked by the client:
POST /api/v1/oauth2/revoke
Content-Type: application/x-www-form-urlencoded
token=eyJhbGciOiJIUzI1NiIs...
&token_type_hint=access_token
&client_id=my-app-client-id
&client_secret=my-app-client-secretThe revocation endpoint always returns 200 OK regardless of whether the token was found, as specified by RFC 7009.
OAuth2 Token Introspection
Resource servers can introspect tokens to verify their validity and retrieve claims:
POST /api/v1/oauth2/introspect
Content-Type: application/x-www-form-urlencoded
token=eyJhbGciOiJIUzI1NiIs...SSO via Keycloak
The IAM service integrates with Keycloak for federated authentication through OIDC and SAML:
OIDC Flow
Browser IAM Service Keycloak
| | |
| GET /api/v1/sso/login | |
|----------------------->| |
| | Build OIDC auth URL |
| 302 Redirect | |
|<-----------------------| |
| |
| Redirect to Keycloak login page |
|------------------------------------------------>|
| |
| User authenticates |
|<------------------------------------------------|
| |
| Redirect with auth code |
|----------------------->| |
| | Exchange code for tokens
| |----------------------->|
| | id_token + userinfo |
| |<-----------------------|
| | |
| | Upsert user in IAM DB |
| | Generate MATIH tokens |
| AuthResponse | |
|<-----------------------| |The SsoController and SsoHandler manage the redirect flow. The KeycloakAdminClient handles token exchange and user information retrieval.
SAML Flow
For enterprise customers that require SAML 2.0 authentication:
| Component | Description |
|---|---|
SamlAuthProvider | Configures SAML service provider metadata |
SamlConfigRepository | Stores per-tenant SAML IdP configuration |
SamlResponseValidator | Validates SAML assertions and signatures |
Risk-Based Authentication
The LoginAnomalyDetector evaluates each login attempt against a risk model that considers multiple signals:
| Signal | Weight | Description |
|---|---|---|
| New IP address | Medium | IP not seen in user's login history |
| New device | High | Device fingerprint not recognized |
| Geo-location anomaly | High | Impossible travel (login from two distant locations in short time) |
| Time anomaly | Low | Login at unusual time for the user |
| Failed attempts | High | Recent failed login attempts from same IP |
| TOR/VPN detection | Medium | Connection from known TOR exit node or VPN |
The risk assessment produces a LoginRiskAssessment response:
public class LoginRiskAssessment {
private RiskLevel riskLevel; // LOW, MEDIUM, HIGH, CRITICAL
private double riskScore; // 0.0 to 1.0
private List<String> riskFactors; // Explanation strings
private boolean requireMfa; // Force MFA even if not enrolled
private boolean blockLogin; // Block the attempt entirely
}Based on the risk level:
| Risk Level | Action |
|---|---|
LOW | Proceed normally |
MEDIUM | Require MFA if enrolled; log warning |
HIGH | Require MFA even if not enrolled; send security alert email |
CRITICAL | Block login; lock account temporarily; alert security team |
The GeoLocationService resolves IP addresses to geographic locations using a local GeoIP database, avoiding external API calls during the authentication hot path.
Account Lockout
The AccountLockoutService implements progressive lockout after failed authentication attempts:
| Attempt | Action |
|---|---|
| 1-3 | No action, just record failure |
| 4 | Warning: "1 attempt remaining" in response |
| 5 | Lock account for 30 minutes |
| 10 (cumulative) | Lock account for 2 hours |
| 20 (cumulative) | Lock account indefinitely; require admin reset |
Lockout state is tracked per user in the database and is tenant-scoped. An admin in one tenant cannot unlock a user in another tenant.
Unlock Endpoints
POST /api/v1/users/{userId}/unlock # Admin endpoint
POST /api/v1/auth/forgot-password # Self-service unlock via password resetToken Refresh Flow
The standard token refresh flow uses the AuthController refresh endpoint:
POST /api/v1/auth/refresh
Content-Type: application/json{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "Bearer",
"expiresIn": 900
}The refresh flow issues a new access token and a new refresh token (token rotation). The old refresh token is invalidated immediately to prevent replay attacks.
Logout
The logout endpoint revokes the refresh token and terminates the associated session:
POST /api/v1/auth/logout
Content-Type: application/json{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Response: 204 No Content
The access token remains valid until its natural expiration (15 minutes by default). For immediate access token invalidation, the client should use the OAuth2 token revocation endpoint or the session revocation endpoint.
Rate Limiting
All authentication endpoints are rate-limited to prevent abuse:
| Endpoint | Limit | Window |
|---|---|---|
POST /api/v1/auth/login | 5 attempts per user | 5 minutes |
POST /api/v1/auth/register | 10 per IP | 1 hour |
POST /api/v1/auth/mfa/verify | 3 attempts per challenge | 10 minutes |
POST /api/v1/auth/refresh | 30 per user | 5 minutes |
POST /api/v1/oauth2/token | 60 per client | 1 minute |
Rate limit state is stored in Redis for distributed enforcement across IAM service replicas. When a limit is exceeded, the service returns 429 Too Many Requests with a Retry-After header.
Next Steps
- Multi-Factor Authentication -- TOTP, SMS, and email MFA enrollment and verification
- Token Lifecycle -- detailed JWT claims structure and rotation
- Session Management -- session tracking and device management