Authentication & sessions
How identity, sessions and access control work, flow by flow.
Complete guide to Orazaka's authentication architecture (local credentials, OAuth2, password recovery).
1. Overview
| Strategy | Module | Configuration | Description |
|---|---|---|---|
| Local Credentials | krizaka-users-core | orazaka.identity.auth.local | Email + password hashed with BCrypt |
| OAuth2 Exchange | krizaka-users-core | orazaka.identity.auth.oauth2.* | NextAuth verifies -> Gateway reconciles |
| Password Reset | krizaka-users-core | — | Token-based reset with zero-enumeration |
Important. The backend never performs OAuth2 redirects or protocol negotiations. This is handled by NextAuth (BFF). The backend only validates tokens.
2. Local Credential Flow
Participants
- Browser (NextAuth)
- orazaka-web-client
- orazaka-edge
- krizaka-users-service
- krizaka-users-core
- krizaka-users-persistence
- PostgreSQL · krizaka_users_db
- Step 1Browser (NextAuth) → orazaka-web-client: POST /api/auth/login {email, password}
- Step 2orazaka-web-client → orazaka-edge: POST /api/v1/auth/login
- Step 3orazaka-edge → krizaka-users-service: route /api/v1/auth
- Step 4krizaka-users-service → krizaka-users-core: IdentityService.authenticate(email, password)
- Step 5krizaka-users-core → krizaka-users-persistence: find the user by e-mail
- Step 6krizaka-users-persistence → PostgreSQL · krizaka_users_db: select the user
- Step 7krizaka-users-core → krizaka-users-core: BCrypt.matches — the hash never leaves the core
- Step 8krizaka-users-core replies to krizaka-users-service: User domain record
- Step 9krizaka-users-service replies to orazaka-web-client: session JWT
- Step 10orazaka-web-client replies to Browser (NextAuth): session cookie
- Endpoint:
POST /api/v1/auth/login - Payload:
{"email": "...", "password": "..."}
3. OAuth2 Token-Exchange Flow
Participants
- Browser (NextAuth)
- Google / GitHub
- orazaka-web-client
- orazaka-edge
- krizaka-users-service
- krizaka-users-core
- krizaka-users-persistence
- Step 1Browser (NextAuth) → Google / GitHub: OAuth2 authorization (redirects handled by NextAuth)
- Step 2Google / GitHub replies to Browser (NextAuth): ID token
- Step 3Browser (NextAuth) → orazaka-web-client: POST /api/auth/callback {provider, idToken}
- Step 4orazaka-web-client → orazaka-edge: POST /api/v1/auth/oauth
- Step 5orazaka-edge → krizaka-users-service: route /api/v1/auth
- Step 6krizaka-users-service → krizaka-users-core: IdentityReconciliationService.reconcile(provider, idToken)
- Step 7krizaka-users-core → krizaka-users-core: OAuth2ProviderVerifier — verify the token, extract the profile
- Step 8krizaka-users-core → krizaka-users-persistence: find the identity by provider and provider id
- Step 9if the user existskrizaka-users-core → krizaka-users-core: reuse the account
- Step 10else a new userkrizaka-users-core → krizaka-users-persistence: save the new user and its identity
- Step 11krizaka-users-core replies to krizaka-users-service: User domain record
- Step 12krizaka-users-service replies to orazaka-web-client: session JWT
- Step 13orazaka-web-client replies to Browser (NextAuth): session cookie
- Endpoint:
POST /api/v1/auth/oauth - Payload:
{"provider": "google", "idToken": "..."}
4. Password Recovery Flow
Participants
- Browser / CLI
- orazaka-web-client
- orazaka-edge
- krizaka-users-service
- krizaka-users-core
- krizaka-users-persistence
- Request a reset
- Step 1Browser / CLI → orazaka-web-client: POST /api/auth/forgot {email}
- Step 2orazaka-web-client → orazaka-edge: POST /api/v1/auth/forgot
- Step 3orazaka-edge → krizaka-users-service: route /api/v1/auth
- Step 4krizaka-users-service → krizaka-users-core: PasswordRecoveryService.requestPasswordReset(email)
- Step 5krizaka-users-core → krizaka-users-persistence: find the enabled user by e-mail
- Step 6if the user existskrizaka-users-core → krizaka-users-core: generate a 48-byte token, keep only its SHA-256 hash
- Step 7if the user existskrizaka-users-core → krizaka-users-persistence: save the reset token (hash, 15 min)
- Step 8krizaka-users-service replies to Browser / CLI: 200 OK — always, so an address cannot be probed
- Reset the password
- Step 9Browser / CLI → orazaka-web-client: POST /api/auth/reset {token, newPassword}
- Step 10orazaka-web-client → orazaka-edge: POST /api/v1/auth/reset
- Step 11orazaka-edge → krizaka-users-service: route /api/v1/auth
- Step 12krizaka-users-service → krizaka-users-core: PasswordRecoveryService.resetPassword(token, newPassword)
- Step 13krizaka-users-core → krizaka-users-persistence: find the token by its SHA-256 hash
- Step 14if the token is valid and activekrizaka-users-core → krizaka-users-core: BCrypt.encode, outside the transaction
- Step 15if the token is valid and activekrizaka-users-core → krizaka-users-persistence: update the password hash, delete the token
- Step 16if the token is valid and activekrizaka-users-service replies to Browser / CLI: 200 OK
- Step 17else the token is invalid or expiredkrizaka-users-service replies to Browser / CLI: 400 Bad Request
- Forgot Password:
POST /api/v1/auth/forgot. Enforces Zero-Enumeration Invariant: returns200 OKregardless of whether the email exists. - Reset Password:
POST /api/v1/auth/resetwith payload{"token": "...", "newPassword": "..."}. Returns400on invalid/expired token.
5. Security & Invariants
- Token Cryptography: 48-byte
SecureRandomtoken saved as SHA-256 hash (never plaintext). Expire in 15 minutes. Single-use. - Password Hashing: BCrypt is always executed outside
@Transactionalblocks to prevent pool starvation. - Provider Extensibility: Implement
OAuth2ProviderVerifierand annotate with@ConditionalOnPropertyto support new sign-in options. - Database Schemas: Password reset tokens and user password-change tracking are defined in
infra/local-db/01-schema.sql. Flyway is disabled (spring.flyway.enabled=false); schemas load via Docker init scripts. - Concurrency: Global security context propagation utilizes a
DelegatingSecurityContextExecutorServicewrapper bean.