Authentication
How users log in to Temps, manage their credentials, and recover access.
Login methods
Temps supports several login methods. You can enable or disable them per instance in Settings → Authentication.
Email + Password
Traditional credentials with secure password hashing. Passwords must be at least 8 characters (max 128) and include an uppercase letter, a lowercase letter, a digit, and a special character.
Magic Links
Passwordless login — the user enters their email address and receives a one-time link valid for a short window. More resistant to phishing than passwords because there is no reusable credential to steal.
Single Sign-On (OIDC)
Log in with any standards-compliant OpenID Connect identity provider — Keycloak, Auth0, Okta, Authentik, Zitadel, Microsoft Entra (Azure AD), Google Workspace, and others. Features:
- PKCE authorization code flow with nonce/state validation
- Just-in-time user provisioning on first login
- Group-to-role mapping (IdP groups → Temps roles)
- Per-provider
trust_idp_emailflag for corporate IdPs that don't emitemail_verified
See OIDC SSO for setup instructions and the full hardening reference.
Two-Factor Authentication
Users can enable TOTP-based 2FA (Google Authenticator, Authy, 1Password, and any RFC 6238-compatible app) from their account settings. Once enabled:
- Every login with email + password or magic link requires a TOTP code
- Changing the account password requires passing the MFA challenge before the change is committed
- API keys bypass MFA (they are pre-authorized tokens scoped to specific permissions)
Admins can require 2FA for all members of a project via Settings → Security.
API Keys
API keys are long-lived tokens for programmatic and CI/CD access. They are scoped to specific permissions and can be revoked at any time.
Creating a key
Go to Settings → API Keys → Create Key. Choose a name, expiry (or no expiry), and one or more permission scopes.
Available scopes
| Scope | What it allows |
|---|---|
read:projects | View projects and their settings |
write:projects | Create and modify projects |
read:deployments | View deployments and build logs |
write:deployments | Trigger and manage deployments |
read:analytics | View analytics data |
admin:project | Full access to a single project |
Best practices
- Create one key per integration — never share keys across systems
- Set an expiry for keys used in ephemeral environments (CI/CD)
- Rotate keys regularly and revoke any that are no longer in use
- Use the narrowest scope that the integration actually needs
Account security
| Property | Behavior |
|---|---|
| Password storage | Bcrypt hash — the plaintext is never stored |
| Session management | Sessions are stored server-side; logout invalidates the session immediately |
| Token expiration | Session tokens have a configurable maximum lifetime |
| Brute-force protection | Repeated failed logins trigger a lockout with exponential back-off |
| Login enumeration | Error messages are constant ("Invalid email or password") regardless of whether the address exists |
Self-service password reset
Users who are locked out can request a password reset from the login screen. The reset link is delivered only by email to the requesting user — it is never forwarded to Slack, webhooks, or admin inboxes.
Requirements: An email provider must be configured and enabled. A Slack-only notification setup does not count. The "Forgot password?" link on the login form appears only when the server reports password_reset_available: true via GET /api/auth/email-status.
How it works:
- The user enters their email on
/forgot-password. The response is always the same ("if an account exists, a link has been sent") regardless of whether the address exists — preventing user enumeration. - The reset email links to
{base_url}/auth/reset-password?token=…. The token expires in 1 hour. - The user sets a new password. Complexity rules match the backend: ≥8 characters (max 128), uppercase, lowercase, digit, and special character.
Endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/api/auth/password-reset/request | POST | Request a reset link (always 200; 503 if no email provider) |
/api/auth/password-reset/verify | POST | Set a new password using { token, new_password } |
/api/auth/email-status | GET | Returns email_configured, magic_link_available, password_reset_available |
In-app password change
Logged-in users can change their password from the account settings page — no email link or logout required. This is distinct from the password reset flow above, which is for users who are already locked out.
Where to find it: Account settings (profile page), available to authenticated users only.
How it works:
- The user enters their current password to re-authenticate before any change is accepted.
- If MFA is enabled, the user must pass the TOTP challenge after the current-password check. The change is not committed until MFA succeeds.
- The user enters and confirms a new password (same complexity rules apply).
- On success, all other active sessions are revoked — only the session that performed the change remains alive. This ensures that if the old credentials were compromised, any concurrent attacker sessions are immediately terminated.
Comparison:
| In-app password change | Self-service password reset | |
|---|---|---|
| Who can use it | Logged-in users | Logged-out users (forgot password) |
| Authentication | Current password + optional MFA | Email reset link (expires in 1 hour) |
| Session effect | All other sessions revoked | No effect on existing sessions |
| Email provider required | No | Yes |