Skip to content

Authentication

MINT supports two complementary authentication methods: a classic email + password flow that issues short-lived JWTs, and WebAuthn / passkey login that uses a hardware security key, Touch ID, Windows Hello, or any other passkey-capable authenticator.

[Screenshot: login page showing email-and-password form and "Sign in with passkey" button]

At a glance

MethodWhat you rememberServer storesCross-device
Password + JWTA passwordA salted password hash + your JWT secretYes
Passkey (WebAuthn)Nothing — your device authenticates youA public key onlyPer-device unless you sync via iCloud Keychain / Google Password Manager

Both methods can be enabled at the same time — users can register a passkey on top of their password account and choose either at login.

Sign in with email + password

Standard form on the login page. On success, MINT issues a JWT with a 24-hour expiry, stored in an HttpOnly cookie. JWTs are refreshed automatically on each authenticated request.

SettingDefaultWhere
Token TTL24 hoursauth.jwtTtlHours in config.json
JWT secretRequiredauth.jwtSecret (32+ random bytes; never commit it)
Password hashingbcrypt, cost 12Implementation detail of auth_service

Rotate the JWT secret carefully

Rotating auth.jwtSecret invalidates every active session, signing every user out. Plan rotations during a maintenance window.

Sign in with a passkey

If passkeys are enabled (auth.passkeysEnabled: true), users can register one or more authenticators on Settings → Security.

[Screenshot: Settings → Security panel listing registered passkeys with last-used timestamps]

Each registered passkey carries:

  • A friendly name (e.g., "Work laptop", "YubiKey 5")
  • The credential ID
  • A public key
  • A last-used timestamp
  • The user agent it was registered from

To log in with a passkey, click Sign in with passkey on the login page; your browser walks you through device unlock and the server verifies the signature. No password is involved.

Recovery and lockout

ScenarioResolution
Lost passkey, no password setThe admin can issue a one-time recovery email from Admin → Users that lets you set a new password and register a fresh passkey.
Forgotten passwordStandard "forgot password" flow; requires SMTP configured.
All authenticators lostAdmin must manually reset, with the same out-of-band identity check the lab uses for any sensitive operation.
Account compromisedAdmin disables the user (revokes JWT, marks credentials inactive), then re-enables after a fresh password and passkey registration.

Single sign-on (SSO)

If your lab uses an external identity provider (Microsoft Entra, Google Workspace, Okta, …), MINT can delegate authentication via OIDC. Enable it in config.json:

json
{
  "auth": {
    "sso": {
      "provider": "oidc",
      "issuer": "https://login.example.org/",
      "clientId": "...",
      "clientSecret": "..."
    }
  }
}

When SSO is configured, the login page redirects to the IdP. Local password and passkey logins remain available for break-glass admin accounts unless you also set auth.localLoginEnabled: false.

Rate limiting

Auth routes are rate-limited to 20 requests per 60 seconds per IP (middleware/rate_limit.py). The limit is skipped automatically in devMode. If you're running behind a proxy that doesn't forward X-Forwarded-For, every request appears to come from the proxy and the limit fires for the whole lab — fix by configuring the proxy to forward client IPs.

Audit and observability

Every successful and failed auth attempt emits a structured log line plus an OpenTelemetry span. Failed attempts include the username and IP; successful attempts include the user ID and the auth method (password / passkey / SSO). Logs are not persisted by MINT itself — ship them to the lab's existing log store.

Next

Members & roles — what an authenticated user can do → Permissions — full RBAC reference

MINT is open source. Made by the Morscher Lab.