Authentication Flows
This page walks through every authentication flow in Vyntech Account — from initial registration to token refresh, MFA verification, and risk challenge handling. Each diagram shows the sequence of interactions between your app, the user, and the identity service.
Overview
Vyntech Account supports seven core authentication flows. Each flow is designed to be stateless at the API level — your application drives the flow by calling the appropriate endpoints in sequence.
Registration
Create a new user account within a tenant
Login
Authenticate with email + password, receive tokens
MFA Verification
Complete login when TOTP MFA is enabled
Token Refresh
Exchange a refresh token for new access + refresh tokens
Password Reset
Forgot password → email link → set new password
Email Verification
Verify the user's email address after registration
Risk Challenge
Additional verification when the risk engine flags suspicious activity
1. Registration Flow
A new user signs up within a tenant. The system validates the password against the tenant's password policy, creates the user record, sends a verification email, and returns tokens so the user is immediately authenticated.
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ Client │ │ id Service │ │ Email Service │
└────┬─────┘ └──────┬───────┘ └───────┬────────┘
│ │ │
│ POST /auth/register │ │
│ {email, password, │ │
│ display_name, │ │
│ tenant_id} │ │
├──────────────────────►│ │
│ │ │
│ │── Validate password │
│ │ against tenant policy │
│ │ │
│ │── Create user (pending) │
│ │ │
│ │── Generate tokens │
│ │ │
│ │ Send verification email │
│ ├─────────────────────────►│
│ │ │
│ 201 {user, tokens} │ │
│◄──────────────────────┤ │
│ │ │Key points: The user is authenticated immediately but their email_verified field is false. You can restrict access to verified users only by checking this field in your app logic. The verification email contains a one-time token valid for 24 hours.
Error Scenarios
- 409Email already registered within this tenant
- 422Password does not meet tenant's password policy requirements
- 429Rate limit exceeded — tenant or IP-level throttling
2. Login Flow
The standard login flow validates credentials, assesses risk, and issues tokens. If the user has MFA enabled, the response indicates that a second factor is required instead of returning tokens directly.
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ Client │ │ id Service │ │ Risk Engine │
└────┬─────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
│ POST /auth/login │ │
│ {email, password, │ │
│ tenant_id} │ │
├──────────────────────►│ │
│ │ │
│ │── Verify credentials │
│ │ │
│ │── Assess risk │
│ ├────────────────────────►│
│ │◄────────────────────────┤
│ │ risk_score: 12 │
│ │ │
│ │── Check MFA status │
│ │ │
│ [If NO MFA] │ │
│ 200 {user, tokens} │ │
│◄──────────────────────┤ │
│ │ │
│ [If MFA enabled] │ │
│ 200 {mfa_required, │ │
│ mfa_token} │ │
│◄──────────────────────┤ │
│ │ │MFA branching: When the response contains mfa_required: true, your app should prompt the user for their TOTP code and call POST /auth/verify-mfa with the provided mfa_token. See the MFA Verification flow below.
Risk assessment:Every login attempt is scored by the risk engine. If the score exceeds the tenant's threshold, the Risk Challenge flow is triggered instead of issuing tokens. The risk score considers device fingerprint, IP geolocation, velocity, and impossible travel detection.
3. MFA Verification Flow
When a user with MFA enabled logs in successfully, the login endpoint returns an mfa_tokeninstead of access tokens. Your app must collect the user's TOTP code and submit it to complete authentication.
┌──────────┐ ┌──────────────┐
│ Client │ │ id Service │
└────┬─────┘ └──────┬───────┘
│ │
│ [After login returns │
│ mfa_required: true] │
│ │
│ POST /auth/verify-mfa│
│ {mfa_token, code} │
├──────────────────────►│
│ │
│ │── Validate TOTP code
│ │ (30s window, ±1 step)
│ │
│ │── Generate session
│ │
│ │── Issue tokens
│ │
│ 200 {user, tokens} │
│◄──────────────────────┤
│ │Token expiry: The mfa_tokenis valid for 5 minutes. If the user doesn't submit their code in time, they must restart the login flow. The TOTP code accepts a ±1 step tolerance (previous and next 30-second window).
Error Scenarios
- 401Invalid TOTP code — user entered the wrong code
- 401Expired MFA token — the 5-minute window has passed
- 429Too many failed attempts — locked out temporarily (configurable per tenant)
4. Token Refresh Flow
Access tokens are short-lived (default: 15 minutes). When they expire, use the refresh token to obtain a new pair. Refresh tokens are single-use — each refresh returns a new refresh token and invalidates the old one (rotation).
┌──────────┐ ┌──────────────┐
│ Client │ │ id Service │
└────┬─────┘ └──────┬───────┘
│ │
│ POST /auth/refresh │
│ {refresh_token} │
├──────────────────────►│
│ │
│ │── Validate refresh token
│ │ (not expired, not revoked)
│ │
│ │── Rotate: invalidate old
│ │ refresh token
│ │
│ │── Issue new access_token
│ │ + new refresh_token
│ │
│ 200 {tokens} │
│◄──────────────────────┤
│ │Rotation security: If a refresh token is used twice (indicating potential theft), the entire session is invalidated. All tokens for that session become invalid, forcing the user to re-authenticate.
Token Lifetimes (Defaults)
Access Token
15 minutes
EdDSA-signed JWT
Refresh Token
7 days
Opaque, single-use
Session
30 days
Configurable per tenant
5. Password Reset Flow
A two-step flow: first the user requests a reset (providing their email), then they set a new password using the token delivered via email. The reset token is single-use and time-limited.
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ Client │ │ id Service │ │ Email Service │
└────┬─────┘ └──────┬───────┘ └───────┬────────┘
│ │ │
│ POST /auth/forgot- │ │
│ password │ │
│ {email, tenant_id} │ │
├──────────────────────►│ │
│ │ │
│ │── Generate reset token │
│ │ (valid 1 hour) │
│ │ │
│ │ Send reset email │
│ ├─────────────────────────►│
│ │ │
│ 200 {message} │ │
│◄──────────────────────┤ │
│ │ │
│ ─── User clicks email link ─── │
│ │ │
│ POST /auth/reset- │ │
│ password │ │
│ {token, new_password} │ │
├──────────────────────►│ │
│ │ │
│ │── Validate token │
│ │── Validate new password │
│ │ against policy │
│ │── Update password hash │
│ │── Revoke all sessions │
│ │ │
│ 200 {message} │ │
│◄──────────────────────┤ │
│ │ │Security note: The forgot-password endpoint always returns 200 regardless of whether the email exists. This prevents user enumeration attacks. All existing sessions are revoked on password reset to ensure any compromised sessions are invalidated.
6. Email Verification Flow
After registration, the user receives a verification email with a one-time token. Clicking the link (or submitting the token via API) marks their email as verified. Users can request a new verification email if the original expires.
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ Client │ │ id Service │ │ Email Service │
└────┬─────┘ └──────┬───────┘ └───────┬────────┘
│ │ │
│ [Registration sends │ │
│ verification email │ │
│ automatically] │ │
│ │ │
│ ─── User clicks email link ─── │
│ │ │
│ POST /auth/verify- │ │
│ email │ │
│ {token} │ │
├──────────────────────►│ │
│ │ │
│ │── Validate token │
│ │── Mark email_verified │
│ │ │
│ 200 {message} │ │
│◄──────────────────────┤ │
│ │ │
│ ─── If token expired ─── │
│ │ │
│ POST /auth/resend- │ │
│ verification │ │
│ {email, tenant_id} │ │
├──────────────────────►│ │
│ │ │
│ │── Generate new token │
│ │ (valid 24 hours) │
│ │ │
│ │ Send verification email │
│ ├─────────────────────────►│
│ │ │
│ 200 {message} │ │
│◄──────────────────────┤ │
│ │ │Rate limiting: The resend-verification endpoint is rate-limited to 3 requests per hour per user to prevent email flooding. The verification token is valid for 24 hours.
7. Risk Challenge Flow
When the risk engine detects suspicious activity during login (new device, unusual location, impossible travel, velocity anomaly), it can escalate the authentication by requiring additional verification before issuing tokens.
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ Client │ │ id Service │ │ Risk Engine │
└────┬─────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
│ POST /auth/login │ │
│ {email, password, │ │
│ tenant_id, │ │
│ device_fingerprint, │ │
│ ip, user_agent} │ │
├──────────────────────►│ │
│ │ │
│ │── Verify credentials ✓ │
│ │ │
│ │── Assess risk │
│ ├────────────────────────►│
│ │◄────────────────────────┤
│ │ risk_score: 85 │
│ │ reasons: [new_device, │
│ │ impossible_travel] │
│ │ │
│ │── Score > threshold │
│ │ → Challenge required │
│ │ │
│ 200 {challenge_ │ │
│ required: true, │ │
│ challenge_type: │ │
│ "email_otp", │ │
│ challenge_token} │ │
│◄──────────────────────┤ │
│ │ │
│ ─── User receives email with OTP ─── │
│ │ │
│ POST /auth/verify- │ │
│ challenge │ │
│ {challenge_token, │ │
│ code} │ │
├──────────────────────►│ │
│ │ │
│ │── Validate OTP │
│ │── Mark device trusted │
│ │── Issue tokens │
│ │ │
│ 200 {user, tokens} │ │
│◄──────────────────────┤ │
│ │ │Risk factors scored:Device fingerprint mismatch, new IP address, impossible travel (two logins from distant locations within an impossible timeframe), login velocity (too many attempts in a short period), Tor/VPN exit node detection, and time-of-day anomaly for the user's historical pattern.
Challenge Types
email_otp6-digit code sent to the user's registered email
mfa_requiredForces MFA verification even for non-MFA users (admin override)
blockLogin denied entirely — admin notification sent
allow_with_flagLogin succeeds but session is flagged for monitoring
Complete Login Decision Tree
The login endpoint combines credential verification, risk assessment, and MFA into a single decision tree. Here's the full logic:
POST /auth/login
│
├── Credentials invalid? ──────── 401 Unauthorized
│
├── Account suspended? ────────── 403 Forbidden
│
├── Account locked (too many ──── 423 Locked
│ failed attempts)?
│
├── IP blocked by allowlist? ──── 403 Forbidden
│
├── Risk score > block ────────── 403 + admin alert
│ threshold?
│
├── Risk score > challenge ─────── 200 {challenge_required}
│ threshold? → verify-challenge
│
├── MFA enabled? ──────────────── 200 {mfa_required}
│ → verify-mfa
│
└── All clear ─────────────────── 200 {user, tokens}Implementation tip: Your login UI should handle all three success responses: direct tokens, MFA required, and challenge required. Use the response shape to route the user to the appropriate next step in your frontend flow.
Token Structure
Access tokens are EdDSA-signed JWTs (Ed25519). The public key is available at the JWKS endpoint for verification. Here's the decoded JWT payload structure:
{
"sub": "usr_01H8KXYZ4F2B7NQ9RPWT3M6J",
"tid": "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
"email": "jane@acme-corp.com",
"roles": ["admin", "editor"],
"permissions": ["users:read", "users:write", "posts:write"],
"session_id": "ses_01H9MNOP5R7T2V4X6Z8B0D2F",
"iat": 1690000000,
"exp": 1690000900,
"iss": "https://id.vyntech.com.au",
"aud": "vyntech-account"
}Claims Reference
substringSubject — the unique user ID (prefixed usr_). This is the primary identifier you use to look up the user in your application.
tidstringTenant ID — identifies which tenant (organization) this user belongs to. Use this to scope all data queries to the correct tenant.
emailstringThe user's email address. Always lowercase. Useful for display but do not use as a primary key — use sub instead.
rolesstring[]Array of role names assigned to this user within their tenant. Roles are defined per-tenant and can carry permissions.
permissionsstring[]Flattened list of all permissions granted through the user's roles. Format is resource:action (e.g., users:read, posts:write). Use these for fine-grained authorization checks.
session_idstringThe active session identifier (prefixed ses_). You can use this to revoke a specific session via the Sessions API.
iatnumberIssued At — Unix timestamp (seconds) when the token was created. Used to determine token age.
expnumberExpiration — Unix timestamp (seconds) when the token expires. Default is 15 minutes after iat. Your app must reject tokens past this time.
issstringIssuer — always https://id.vyntech.com.au. Verify this matches to prevent tokens from other issuers being accepted.
audstringAudience — identifies the intended recipient of the token. Verify this matches your application's expected audience.
Verification Endpoints
Use these public endpoints to discover configuration and retrieve public keys for token signature verification. No authentication is required.
OpenID Connect Discovery document. Returns all supported endpoints, scopes, algorithms, and grant types. Your OIDC client library uses this to auto-configure itself.
{
"issuer": "https://id.vyntech.com.au",
"authorization_endpoint": "https://id.vyntech.com.au/oauth/authorize",
"token_endpoint": "https://id.vyntech.com.au/oauth/token",
"userinfo_endpoint": "https://id.vyntech.com.au/oauth/userinfo",
"jwks_uri": "https://id.vyntech.com.au/.well-known/jwks.json",
"registration_endpoint": "https://id.vyntech.com.au/oauth/register",
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["EdDSA"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"],
"code_challenge_methods_supported": ["S256"],
"revocation_endpoint": "https://id.vyntech.com.au/oauth/revoke",
"introspection_endpoint": "https://id.vyntech.com.au/oauth/introspect"
}Field Explanations
issuerThe base URL of the identity provider. Must match the iss claim in tokens you validate.authorization_endpointWhere your app redirects users to start the OAuth 2.1 authorization code flow.token_endpointWhere your backend exchanges an authorization code for access + refresh tokens.userinfo_endpointReturns claims about the authenticated user (sub, email, name, roles) when called with a valid access token.jwks_uriURL to the JSON Web Key Set containing public keys for verifying token signatures.scopes_supportedAvailable OAuth scopes. openid is required for OIDC, offline_access enables refresh tokens.response_types_supportedOnly 'code' — OAuth 2.1 mandates the authorization code flow (no implicit flow).grant_types_supportedauthorization_code for initial auth, refresh_token for token renewal.id_token_signing_alg_values_supportedEdDSA (Ed25519) only — faster and more secure than RS256.code_challenge_methods_supportedS256 (PKCE) — required for all public clients, recommended for confidential clients.JSON Web Key Set containing the Ed25519 public key(s) used to sign access tokens and ID tokens. Cache this response and refresh periodically (keys may rotate). Most JWT libraries handle this automatically.
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "key_01H8ABCD1234",
"use": "sig",
"alg": "EdDSA",
"x": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"
}
]
}Field Explanations
ktyKey Type — 'OKP' (Octet Key Pair) indicates an Edwards-curve key, used for EdDSA signatures.crvCurve — 'Ed25519' specifies the Edwards curve variant. This is the only curve supported.kidKey ID — unique identifier for this key. The JWT header's kid claim references this value so you know which key to use for verification.useKey Usage — 'sig' means this key is used for signing (not encryption). Only signature keys are served here.algAlgorithm — 'EdDSA' confirms the signing algorithm. Your JWT verification library needs to support EdDSA/Ed25519.xPublic Key — the base64url-encoded 32-byte Ed25519 public key. This is what your library uses to verify token signatures.Key rotation: Keys may be rotated periodically for security. When rotation happens, both the old and new key are served in the JWKS for a transition period (typically 24 hours). Always match the kid from the JWT header to the correct key in the set. Never hardcode a specific public key — always fetch from the JWKS endpoint.
What's Next
Authentication API Reference →
Full endpoint reference with request/response examples for every auth endpoint
MFA Guide →
How to set up and manage TOTP-based multi-factor authentication
Risk Engine Guide →
Configure risk thresholds, challenge types, and device trust
Session Management Guide →
Control session lifetimes, concurrent sessions, and forced logout