Authentication Guide
Overview
Keymaster supports multiple authentication methods, all unified behind a single login page per app. Users choose their preferred method; Keymaster handles the rest.
Supported Auth Methods
| Provider | Type | Setup Required |
|---|---|---|
password |
Email + password (argon2id hashed) | None — built-in |
magic_link |
Passwordless email link (15-min expiry) | Email infrastructure |
google |
Google OAuth 2.0 | Google Cloud Console credentials |
github |
GitHub OAuth | GitHub Developer Settings credentials |
microsoft |
Microsoft Entra ID | Azure App Registration |
apple |
Sign in with Apple | Apple Developer Program |
Apps choose which providers to enable via the Console. The login page only shows enabled providers.
OAuth2 Authorization Code Flow
This is the primary flow for web applications.
1. Initiate Login
Redirect the user to:
GET https://keymaster.cloud-monitor.com/login
?app_id={uuid}
&redirect_uri={your_callback_url}
Optional parameters:
- state={opaque} — Opaque value echoed back to your redirect_uri where supported (use it for CSRF protection / round-trip context)
- prompt=login — Force login screen even if SSO session exists (skip SSO fast path)
- code_challenge={base64url(sha256(verifier))} + code_challenge_method=S256 — PKCE parameters. Recommended for web apps. When a challenge is sent, PKCE is enforced at exchange time.
2. User Authenticates
Keymaster shows the branded login page. The user picks their auth method: - Password → email + password form - Magic Link → email input, receives link via email - OAuth → redirects to provider (Google, GitHub, etc.), returns to Keymaster
3. SSO Fast Path
If the user has an active km_sso session (logged in at Keymaster within the last 8 hours), Keymaster skips the login screen and immediately redirects back to your redirect_uri — true cross-app SSO. The callback delivery is scheme-gated exactly as in step 4 (web receives a ?code= to exchange; native custom-scheme apps receive tokens directly).
This only happens if:
- User has a valid km_sso cookie
- User is enrolled in the target app
- prompt=login is NOT set
4. Callback (Scheme-Gated Delivery)
On success, Keymaster redirects back to your redirect_uri. What arrives depends on the scheme of the redirect_uri:
redirect_uri scheme |
Callback query | Next step |
|---|---|---|
http / https (web) |
?code={one_time_code} |
Must be exchanged at /token/exchange (step 5). PKCE is enforced if a code_challenge was sent. |
Native custom scheme (e.g. myapp://callback) |
?access_token={jwt}&refresh_token={opaque} |
Use the tokens directly — no exchange needed. The OS routes the deep link straight to the app. |
Web apps do NOT receive tokens in the callback URL. Reading
access_tokenfrom the callback is the OLD behavior; it no longer works for web apps and causes an infinite login redirect loop. Web apps receive a short-livedcodethat must be exchanged server-side (step 5).
5. Exchange the Code (Web Apps)
Web apps exchange the one-time code for tokens server-side (never expose the exchange to the browser):
POST https://keymaster.cloud-monitor.com/token/exchange
Content-Type: application/json
{
"code": "{code}",
"app_id": "{uuid}",
"code_verifier": "{verifier}" // include ONLY if you started login with PKCE
}
Response:
200 OK
{
"access_token": "{jwt}",
"refresh_token": "{opaque}",
"token_type": "Bearer",
"expires_in": 900
}
The code is single-use and expires in 60 seconds — exchange it immediately in your callback handler.
Native custom-scheme apps skip this step; they already have tokens from the deep link.
6. Verify the Access Token
The access token is an RS256-signed JWT. Fetch the public keys from GET /.well-known/jwks.json and verify:
issequalshttps://keymaster.cloud-monitor.comaudequals yourapp_idexpis in the futuretoken_typeequals"access"
Claims include: sub, email, name, roles[], aud, iss, iat, exp, token_type. See the Quick Start Guide for verification code, and Token Lifecycle for refresh and full session lifecycle.
7. Error Handling
On failure, Keymaster returns the user to its own hosted login page with an error parameter ({KEYMASTER_URL}/login?error={reason}) — it does not deliver a code or tokens to your redirect_uri:
| Error | Meaning |
|---|---|
oauth_denied |
User cancelled the OAuth consent screen |
oauth_failed |
Provider error (network, invalid response) |
not_enrolled |
User exists but isn't enrolled in this app |
pending_approval |
User's access request is pending admin approval |
suspended |
User's access to this app has been suspended |
provider_not_allowed |
Auth method not enabled for this app |
Migrating from Token-in-URL
Earlier versions delivered tokens to web apps directly in the callback URL ({redirect_uri}?access_token=...&refresh_token=...). This no longer works for web (http/https) redirect URIs — the callback now delivers a ?code= instead, and reading access_token from the URL causes an infinite login redirect loop.
To migrate an existing web integration:
- In your callback handler, read
code(anderror) from the query string instead ofaccess_token/refresh_token. - Exchange the
codeserver-side atPOST /token/exchange(step 5) to obtain the tokens. - (Recommended) Add PKCE: generate a
code_verifier, sendcode_challenge/code_challenge_method=S256on the login redirect, and pass thecode_verifierin the exchange.
Native custom-scheme apps are unaffected — they still receive tokens directly on the deep link.
Registration Policies
Each app has a registration policy that controls how users gain access:
| Policy | Behavior |
|---|---|
open |
Anyone can sign up. Users are auto-enrolled on first login. |
invite |
Requires an invite code. Users without enrollment see the invite code page. |
approval |
Anyone can request access. Admin must approve before access is granted. |
Cross-App SSO
Keymaster's SSO works via the km_sso httponly cookie on the Keymaster domain.
Flow:
1. User logs into App A → Keymaster sets km_sso cookie (8-hour lifetime)
2. User visits App B → App B redirects to Keymaster login
3. Keymaster sees valid km_sso → checks user is enrolled in App B
4. If enrolled → issues tokens and redirects back immediately (no login screen)
5. If not enrolled → shows login page with "You don't have access" message
SSO does NOT auto-enroll users. Each app controls its own enrollment via registration policy.
Passwordless Accounts
As of v2.4.0, password is optional on all registration flows. Users can create accounts entirely via OAuth (Google, GitHub, Microsoft, Apple) without ever setting a password. This applies to open signup, invite redeem, and accept-invite flows.
OAuth-only users can add a password later from the Account page (POST /account/set-password). Until a password is set, the user can only authenticate via their linked OAuth provider(s) or magic link.
Password Policy
- Minimum length: 16 characters (platform default, configurable per tenant)
- Hashing: Argon2id
- No complexity requirements enforced (length is the primary defense)
- Password change revokes all SSO sessions (forces re-authentication everywhere)
Two-Factor Authentication (TOTP)
- Apps can require 2FA via the "Require 2FA" toggle in Console
- Users set up TOTP via the Account page (any authenticator app)
- 8-character backup codes provided (10 codes, single-use)
- When 2FA is required but not set up, login returns a structured error with a link to the Account page
Magic Links
Passwordless email login:
1. User enters email on login page
2. Keymaster sends a signed link (15-min expiry, single-use)
3. User clicks link → Keymaster verifies, creates session, redirects back via the scheme-gated callback (step 4 — web receives a ?code= to exchange, native receives tokens)
4. Link is consumed — cannot be reused
Magic links are ideal for infrequent users or kiosk environments where typing passwords is impractical.
OAuth Provider Notes
- Uses OpenID Connect (email + profile scopes)
- Email verified by Google is automatically marked verified in Keymaster
prompt=select_accountforces account picker
GitHub
- Uses GitHub's OAuth2 flow
- Email may require a separate API call if user's GitHub email is private
Microsoft
- Uses Microsoft Entra ID (formerly Azure AD)
- Supports both personal and work/school accounts
prompt=select_accountforces account picker
Apple
- Uses Sign in with Apple (JWT-based)
- Apple only sends the user's name on first login — Keymaster stores it
- Requires Apple Developer Program membership