Security Best Practices
A short, actionable checklist for the two people who decide how secure a LumoAuth deployment is: the integrator wiring an application to it, and the organization admin configuring it. Every item reflects how the product behaves today; where a default already does the safe thing, the item says so. For the underlying rules see Credentials and access model.
For integrators
Choose the narrowest credential
| Do this | Why |
|---|---|
Use an API key with only the scopes the job needs (admin:users:read, not admin). One key per job. | A key with a single scope only reaches that resource, and a :read scope cannot mutate. A leaked narrow key is a small problem. See API keys. |
For OAuth-based automation, request an admin scope on the token (admin or admin:<resource>:<read|write>). | openid profile email tokens get 403 on the Admin API even for administrators. Only first-party clients can be granted admin scopes. See Admin API authentication. |
| Send the credential on every request. Never expect a cookie back. | API keys and Bearer tokens are stateless: they never create a browser session and are refused on the portal and account pages. |
Use the SCIM provisioning token (SCIM is the standard for automatic user provisioning) (or an access token with the scim scope) for SCIM, never HTTP Basic with an admin's password. | Basic auth is a single factor; it is refused for users enrolled in MFA (multi-factor authentication), and ID, logout, JIT and agent tokens are refused on SCIM. See SCIM API. |
Validate what comes back
| Do this | Why |
|---|---|
Validate iss on the authorization response and in every token against the issuer in the discovery document (byte-compare). | Defends against mix-up attacks (the issuer identifier in the response). LumoAuth's iss is always identical to discovery: …/orgs/{slug}/api/v1 or your custom domain. See OAuth security. |
Check aud, exp and the signature against jwks_uri; use introspection when you need instant revocation. | A valid signature alone does not prove the token was meant for you. |
| Use PKCE (a one-time secret that protects the sign-in code) for every client and exact-match redirect URIs. | Required for public clients; javascript: and data: redirect URIs are rejected at registration. |
Turn on DPoP for tokens that leave a server, and keep the server-provided nonce off unless every client implements the use_dpop_nonce retry. | DPoP (a sender-constrained token) binds a token to a key so theft is useless. The nonce is opt-in per organization via settings.security.dpop_require_nonce because enabling it breaks clients that do not retry. See Token management. |
Send the CSRF token with POST /logout and with session-authenticated Admin API writes. | All logout links are token-bearing forms; a bare POST /logout is refused. |
Standard
Issuer identifier in the response. Solves
mix-up attacks where a response is replayed from a different authorization
server. The one thing to do: compare the iss parameter on the callback with
the discovery issuer before exchanging the code.
See the standards reference.
For organization admins
Authentication
| Do this | Why |
|---|---|
| Offer passkeys (sign-in with a device biometric or security key) and require MFA (multi-factor authentication) for admins. | Passkeys are phishing-resistant and always require user verification (biometric or PIN); assertions without it are rejected. See Passkeys. |
| Turn on Adaptive MFA and breached-password checks. | Step-up only on risky sign-ins; block reused breached passwords. See Adaptive MFA. |
| Keep the default lockout and rate limits. | The hosted OAuth login page, the form login and the LDAP path share the same limiter and lockout accounting, keyed by IP and e-mail, and return one generic failure message. |
Leave allow_root_admin_login off (the default). | It lets LumoAuth operators sign in through your hosted login page. It is changed only by LumoAuth operators at your request; every such login is audited at critical severity. See Organization settings. |
Provisioning and access control
| Do this | Why |
|---|---|
| Review the SCIM policy toggles before connecting an IdP: user creation, updates, deletion, group operations, protected attributes and the deprovisioning action. | These are enforced by the SCIM API, not just shown in the portal. A protected attribute is left untouched on update; a disallowed operation is refused. Set them at /orgs/{orgId}/portal/integrations/scim. See SCIM provisioning. |
| Give default roles for social login no admin-conferring permissions. | Admin and system roles are rejected as social-login defaults, and a role can never confer more than the assigning actor holds. |
| Set an allowed reviewers list on ServiceNow and Jira approval channels. | Those channels assert the reviewer's e-mail rather than proving it; the list caps what a leaked callback token can do. See Approval channels. |
| Least privilege everywhere: separate admin roles, review assignments quarterly, use groups. | Fewer people with settings.manage means fewer people who can mint admin credentials. |
Organization security
| Do this | Why |
|---|---|
| Rotate signing keys and client secrets on a schedule. | Limits the window of a compromise. Client secrets are hashed and never re-displayed. See Signing keys. |
| Enable audit logging, alert channels and a SIEM (security information and event management system) export. | Failed logins, lockouts, root-admin fallback logins and scope-check failures are all audited. See Audit logs. |
| Use a custom domain and validate every outbound URL you configure (webhooks, LDAP, log sinks) points where you expect. | Outbound calls are SSRF-guarded and DNS-pinned, but a typo still sends data to the wrong host. |
Common mistakes
403 forbiddenon the Admin API with an OAuth token. The token has noadminoradmin:<resource>:<read|write>scope. Request one on a first-party client, or use an API key.403 insufficient scopewith an API key. The key was created with a narrower scope than the endpoint needs (for exampleadmin:users:readon aPOST). Create a key with the:writescope for that resource.- No session after calling an API with a Bearer token. Expected. Present the credential on every request; log in through the browser for the portal.
- SCIM returns
401with an admin's username and password. The admin has MFA enrolled. Use the provisioning token. - Passkey sign-in fails on a security key without a PIN. User verification is required. Set a PIN on the key or use a platform authenticator.
Related Guides
Standards used on this page
| Plain name | Standard |
|---|---|
| Issuer identifier in the response | RFC 9207 |
| Proof Key for Code Exchange (PKCE) | RFC 7636 |
| Sender-constrained tokens (DPoP) | RFC 9449 |
See the standards reference for what each one covers and how LumoAuth uses it.