MATIH Platform is in active MVP development. Documentation reflects current implementation status.
6. Identity & Access Management
Login Flows

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 StatusConditionResponse Body
401Invalid credentials{ "code": "AUTHENTICATION_FAILED", "message": "Invalid email or password" }
423Account locked{ "code": "ACCOUNT_LOCKED", "message": "Account locked due to too many failed attempts", "retryAfter": 1800 }
403Account 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:

MethodCode Source
TOTPAuthenticator app (Google Authenticator, Authy, 1Password)
SMSSMS message sent to registered phone number
EMAILEmail sent to registered email address
BACKUP_CODEOne-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=S256

The 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_wW1gFWFOEjXk

Response:

{
  "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:

ClaimDescription
client_idThe OAuth2 client that requested the token
scopeSpace-separated list of granted scopes
user_idThe authenticated user's ID
tenant_idThe tenant context
token_typeAlways "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:write

Response:

{
  "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-secret

OAuth2 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-secret

The 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:

ComponentDescription
SamlAuthProviderConfigures SAML service provider metadata
SamlConfigRepositoryStores per-tenant SAML IdP configuration
SamlResponseValidatorValidates SAML assertions and signatures

Risk-Based Authentication

The LoginAnomalyDetector evaluates each login attempt against a risk model that considers multiple signals:

SignalWeightDescription
New IP addressMediumIP not seen in user's login history
New deviceHighDevice fingerprint not recognized
Geo-location anomalyHighImpossible travel (login from two distant locations in short time)
Time anomalyLowLogin at unusual time for the user
Failed attemptsHighRecent failed login attempts from same IP
TOR/VPN detectionMediumConnection 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 LevelAction
LOWProceed normally
MEDIUMRequire MFA if enrolled; log warning
HIGHRequire MFA even if not enrolled; send security alert email
CRITICALBlock 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:

AttemptAction
1-3No action, just record failure
4Warning: "1 attempt remaining" in response
5Lock 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 reset

Token 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:

EndpointLimitWindow
POST /api/v1/auth/login5 attempts per user5 minutes
POST /api/v1/auth/register10 per IP1 hour
POST /api/v1/auth/mfa/verify3 attempts per challenge10 minutes
POST /api/v1/auth/refresh30 per user5 minutes
POST /api/v1/oauth2/token60 per client1 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