Troubleshooting
Common problems integrating with or operating SenseCrypt, and how to fix them — sign-in failures, token and PKCE errors, redirect-URI issues, mutual TLS certificate problems, SCIM problems, refresh-token behavior, and self-hosting first-run failures.
Common problems and their fixes, grouped by where they show up. For the exact error taxonomy see Error codes.
Sign-in and authorization
Users can't sign in — everything returns an opaque error
The access gate is default-closed: an application with zero attached groups admits nobody, and every rejection is deliberately opaque (unknown app, unknown user, suspended user, and missing group all look identical).
Fix: attach at least one group to the application and add the user to it. Confirm the user is active (not suspended or deleted) and, for SCIM-provisioned users, that they have completed biometric enrollment on the app (a pending shell can't sign in). The gate re-runs on every token and refresh, so a removed or suspended user fails on their next call.
Config Doctor is green but my test user is still refused
Config Doctor's access check turns green when anyone at all can reach the application — it evaluates accessible_user_count > 0 || allow_signup. It says nothing about whether this user can. So an application with one populated group passes the check while every user outside that group is refused.
Fix: confirm the specific user is a member of a group attached to this application. The symptom to look for is timing: the phone shows a no-access message immediately, the browser page keeps waiting, and the relying party receives error=access_denied&error_description=session_expired only when the 300-second session lifetime lapses.
invalid_grant at the token endpoint
On the authorization_code grant this is intentionally uniform — it covers an invalid/expired/already-used code, a cross-tenant client, PKCE failures, and access-gate failures. Work through:
- Code already used or expired — authorization codes are single-use and short-lived. Exchange immediately; never retry an exchange with the same code.
- PKCE mismatch — see PKCE errors below.
- Wrong client or redirect_uri — the code is bound to the
client_idandredirect_uriit was issued for; the token request must present the same ones. - Access gate — the user must still pass the group gate at exchange time.
PKCE errors
SenseCrypt requires S256 and rejects plain.
code_challenge is required for this client— the app requires PKCE, so the client must send acode_challengeat/authorizeor/par. New apps require it by default. SPA and FAPI apps always do. A confidential client that cannot send one, such as an Auth0 or Stytch connection, needs Require PKCE turned off in the app's Tokens & Security settings.unsupported code_challenge_method— sendS256(or omit the method with anS256challenge; the implicitplaindefault is not honored).code_verifier does not match code_challenge— your verifier must be the exact preimage:BASE64URL_NOPAD(SHA256(verifier)) == challenge. Check you aren't re-encoding or truncating.code_verifier supplied but session was not registered with PKCE— you sent a verifier for a flow that had no challenge. Send the challenge at authorization, or drop the verifier.
redirect_uri rejected (or an unexplained refusal page at /authorize)
Redirect URIs are matched against an exact-match allow-list: scheme and host are case-insensitive, but the path is byte-exact, so a trailing slash on one side and not the other is a mismatch. At /authorize, an unknown client and a bad redirect_uri return the same branded HTML refusal page — HTTP 400, no redirect and no JSON error — to prevent enumeration, so a "mysterious" 400 page instead of a callback is often just an unregistered redirect URI.
Fix: register the exact callback URL on the application, including scheme, host, port, and path. Note that the post-logout redirect URI has a separate allow-list from the /authorize callback list.
invalid_target when requesting an API audience
The client requested a resource/audience it isn't allowed to mint for. Unknown API and ungranted API are indistinguishable (both fail closed).
Fix: create the resource server and grant the application access to that audience. For M2M, the audience must be exactly the tenant's Management API identifier ("{issuer}/v1/admin/").
No permissions claim in the access token
permissions is emitted only when you target a resource server that has enforce_rbac turned on. Without an audience, or against a resource server that doesn't enforce RBAC, there's no permissions claim by design.
Fix: request the API's audience and enable enforce_rbac on that resource server. Also confirm the user actually has a role (directly or via a group) granting permissions on that API — the claim may legitimately be an empty array.
Tokens and sessions
Refresh suddenly fails with invalid_grant
- Reuse detected — reusing an already-rotated refresh token outside the short overlap window is treated as a breach and revokes the entire token family. Store only the latest token returned and never reuse a superseded one (avoid concurrent refreshes racing on the same token).
- The user lost access — the access gate re-runs on every refresh, so a suspended/deleted user, or one removed from the app's groups, fails closed.
- Absolute expiry — the family has an absolute lifetime fixed at issuance that rotation can't extend.
I revoked a token but the access token still works
Access tokens are self-contained JWTs and remain valid until exp unless explicitly revoked (their jti denylisted, or their refresh family revoked). Keep access-token lifetimes short and refresh as needed. UserInfo and introspection do honor the denylist immediately.
"Logout" doesn't sign the user out
RP-Initiated Logout ends the OP browser session (sc_op_session) and revokes the user's refresh-token families for every application that session signed in to. If you are not being redirected back, the usual cause is a missing id_token_hint — without one SenseCrypt shows a confirmation page and its own signed-out page instead of your post_logout_redirect_uri, and a request naming a post_logout_redirect_uri with no hint and no client_id is refused with a 400 page that signs nothing out. Outstanding access tokens still live until exp unless revoked.
Signature verification fails at my API
- Verify against the tenant's
jwks_uriand select the key bykid. Access tokens are ES256; an id_token uses the algorithm its application registered (ES256 or RS256), so pin that one — anInvalidAlgorithm/alg not allowederror here usually means the verifier is pinned to ES256 for an RS256 application. - Tolerate multiple keys in the JWKS during a rotation overlap.
- If you cache the JWKS, a rotation (especially an immediate one for a compromised key) means your cache may still trust an old key until it expires. Cache with a sane TTL and refetch on an unknown
kid.
Mutual TLS
No response from a mutual TLS endpoint
You call an endpoint from mtls_endpoint_aliases with a client certificate, and no HTTP response comes back. The connection closes before a status code or an error body arrives. Over HTTP/1.1, curl reports (52) Empty reply from server. Over HTTP/2, the connection can end with a TLS protocol version alert. The same request without a certificate gets an HTTP response.
A known cause is a client certificate that is not X.509 v3. On the tenant's <slug>.sensecrypt.com host, the mutual TLS listener refuses an earlier version, such as v1, at the TLS layer. The request does not get to SenseCrypt, so SenseCrypt cannot return an error code. Registration does not check the version, so a successful registration does not prove that the certificate is v3.
Fix: check the version with openssl x509 -in client-cert.pem -noout -text | grep 'Version:'. The output must show Version: 3 (0x2). If it shows a different version, such as Version: 1 (0x0), issue a new certificate. If you make the certificate with openssl req -x509, request at least one extension, for example -addext "extendedKeyUsage=clientAuth". Some openssl builds, such as the LibreSSL build that ships with macOS, make a v1 certificate without one. Check the version of the new certificate. For a self_signed_tls_client_auth application, register the new certificate, because the presented certificate must match the registered one byte for byte. See Sender-constrained tokens.
SCIM provisioning
Filters return 400 invalidFilter
An unknown attribute path or a malformed/over-long filter is a 400 invalidFilter — never a silent full-directory listing. Use supported paths and operators (eq ne co sw ew gt ge lt le pr, and/or/not, grouping, emails[...]). Multi-valued complex attributes support presence (pr) only.
Group member removal isn't working
A remove on members without a target is rejected as 400 invalidValue — it is never treated as clear-all. Send the specific member (members[value eq "<id>"], or the id in the body).
409 uniqueness on create
A userName/email collision (users) or displayName collision (groups) within the tenant. Reconcile the existing record instead of creating a duplicate.
SCIM auth fails (401/403)
- 401 — the bearer token is missing or invalid;
detailnames the reason. Mint a freshssct_token in the console, or check your M2M token. - 403 — the token authenticated but lacks the
scim:read/scim:writecapability for the method. Grant the SCIM capabilities to the M2M app's role.
A SCIM-managed group can't be edited in the console
That's intentional — SCIM-managed groups are read-only in the admin console so your external IdP stays the source of truth. Edit them through your IdP.
Device / mobile
426 upgrade_required
The client's protocol version is below the supported floor. The user must update the Authenticator app.
A device sign-in returns attestation_failed
The tenant's attestation policy is in enforce and the failure is opaque (error: "forbidden", message: "attestation_failed"). Causes include a non-genuine or sideloaded app, a failed device-integrity verdict, a simulator or emulator, or an identity mismatch.
If the tenant runs its own app: compare the build against the console's Mobile App page — package name, signing-certificate fingerprints, required verdict level, team ID, bundle ID, and the App Attest environment (a development-signed build attests against Apple's sandbox, which is refused while Require production App Attest environment is on). An identity field left empty fails closed. Switch the tenant to Audit to see the verdicts in the server log without blocking anyone, then back to Enforce. See Ship your own Authenticator app.
If you self-host: confirm the deployment defaults the stock app is checked against — the Google service-account credentials, and the package name, signing-cert fingerprints, and iOS team/bundle IDs. A missing credential fails the verifier closed. Server logs carry the specific subreason; clients never do. See Self-hosting for the exact configuration.
clock_skew errors from the device
The request timestamp is outside the allowed window. This is recoverable — the device should sync its clock (NTP) and retry.
Console sign-in
No password-reset email arrives
The Forgot Password dialog shows the same sentence for every address. The sentence does not prove that SenseCrypt sent an email. A console account that signs in with SenseCrypt, GitHub, Google or Microsoft has no password, so SenseCrypt sends it no reset email. Sign in with a Continue with button. If you do not remember how you created the account, try the buttons. Use only accounts at the providers that have your console account's email address. See Console sign-in methods.
"This email signs in with a password"
The account is a password account, so a Continue with button cannot sign in to it. Sign in with your email address and password. If you forgot the password, select Forgot password? in the Sign In dialog. See Console sign-in methods.
Rate limiting
Unexpected 429 responses
You've exceeded a rate limit. Read the Retry-After header and back off. Two common surprises:
- Introspecting on every request. Verify the JWT locally against the JWKS and reserve introspection for revocation checks.
- All clients appearing as one IP (self-hosted). If
TRUSTED_PROXY_HOPSdoesn't match your proxy topology, every client can be attributed to a single IP and trip the IP-scoped limits. Set it to the actual number of proxy hops.
Self-hosting first-run failures
For the exact configuration behind each fix below, see Self-hosting.
| Symptom | Cause / fix |
|---|---|
Startup aborts: no active signing-key KEK configured — refusing to boot | No at-rest key-encryption key resolved. The service fails closed rather than store signing keys as plaintext — configure a signing-key encryption key (locally or via KMS). |
| Admin login fails complaining the admin session secret is empty | The admin session secret isn't set. Configure a strong random value and restart. |
API returns 500s with relation "..." does not exist | Database migrations haven't been applied — run them and restart. |
OIDC/SAML endpoints return NotFound | Host resolution is disabled, or the request host doesn't resolve to a tenant slug. Protocol surfaces live on per-tenant subdomain hosts — configure your issuer base domain. |
A 502 minter_unavailable when creating a user from a photo | The sealing step for admin photo provisioning is unreachable. Only admin photo-provisioning depends on it — sign-in, mobile self-signup, and token rotation are unaffected. Check that provisioning is configured. |
| PINs / verification links never arrive | In a development configuration, mail is only logged, not sent — grab PINs and links from the application log. In production, enable email delivery with a DKIM-verified sender. |
| SSE streams (live activity feed, QR page) stall or arrive late | Disable proxy buffering for the SSE routes and keep the SSE heartbeat interval below your proxy's idle timeout. |
Related
- Error codes — every error and its meaning.
- Rate limits — the throttling policy.
- Self-hosting — deployment, configuration, and operations.
- Security best practices — hardening your integration.
Glossary
Definitions of the terms used across the SenseCrypt documentation — protocol, authorization, key-custody, and device concepts, in the precise sense SenseCrypt uses them.
API Reference
The full SenseCrypt REST API — OIDC protocol endpoints, the SAML IdP, SCIM 2.0, and the admin/Management API — generated from the OpenAPI spec, with the auth model and base URL for each surface.