Security
SenseCrypt's security model — biometric blindness, per-tenant issuer isolation, encryption at rest for signing keys, the device-trust model, anti-enumeration design, and compliance posture.
SenseCrypt is a security product, and its architecture centers on one property: it never sees your users' biometrics. This page describes the model in depth — what the service protects, how it protects it, and the boundaries you should design around.
Biometric blindness
Face matching happens only on the user's phone. No face image and no biometric template ever crosses the network to SenseCrypt or to your application.
- Enrollment produces a sealed, opaque face token that only the same live face can open on-device.
- At sign-in, the phone opens that token locally and returns a one-time cryptographic proof. SenseCrypt verifies the proof without ever handling the face itself.
- SenseCrypt cannot decrypt the face token or reconstruct anything about the face. There is deliberately no biometric-decryption code path anywhere in the service.
The practical consequence: a breach of your systems — or of SenseCrypt — exposes no reusable biometric and no password, because neither is ever stored in a usable form.
The one server-side biometric touchpoint
There is exactly one path where a photo transits the server: admin provisioning, when an operator creates a user from an uploaded photo in the console. The image is handled in-memory through an isolated sealing step that returns a sealed face token. The server persists only the sealed token — the image is never written to disk, database, or logs. Device-driven flows (mobile self-signup, and per-sign-in token rotation) seal tokens on the phone and never involve that step at all.
Passwordless multi-factor by construction
Every sign-in combines two factors the user physically controls:
- A device-bound key generated in the phone's secure hardware (Secure Enclave on iOS / StrongBox-backed Keystore on Android), used to sign every request. The server stores only the public key.
- A fresh, live face match performed on the device at each authentication, with liveness detection.
There are no passwords for end users to phish, reuse, or leak. Tokens assert this: SenseCrypt sets amr to reflect the device-key proof and the on-device face match, and uses a face-ceremony acr value.
Per-tenant signing keys
Each tenant is its own OIDC/SAML issuer and signs with its own key — there is no shared, deployment-wide signing key. A token minted for one tenant can never validate against another's.
- OIDC tokens are signed with a per-tenant ES256 key (EC P-256).
- SAML assertions are signed with a per-tenant RSA-2048 key and a self-signed X.509 certificate.
Keys are rotatable with a grace overlap so already-issued tokens keep verifying while relying parties pick up the new key. For a suspected compromise, a tenant's key can be rotated immediately.
Encryption at rest
Signing keys are encrypted at rest. Private key material is sealed with a key-encryption key (KEK), and the KEK itself is held outside the database — so a database dump alone cannot decrypt any signing key. The KEK can optionally be held in AWS KMS for managed custody, and individual tenants can opt to keep their signing key non-exportable inside KMS instead of in the database.
Two properties follow:
- Fail-closed — the service refuses to run without an active KEK, so signing keys can never be stored as plaintext.
- Crypto-shredding — a tenant's sealed key material can be rendered permanently and irreversibly unrecoverable, which supports offboarding, right-to-erasure, and revoking access after a suspected compromise.
Per-tenant issuer isolation
Multi-tenancy is a hard security boundary, not a soft filter:
- Each tenant is its own issuer, resolved from the request host (a forged
X-Forwarded-Hostis validated against the issuer base domain and dropped if it isn't a host the deployment actually serves). - M2M tokens are bound to exactly one tenant; face tokens are bound to one tenant via
context_id. - Admin queries load rows through tenant-scoped SQL helpers that filter the tenant foreign key in the query itself — never
WHERE id = :idplus a Python-side check. - The OIDC authorization code and PAR
request_uriare bound to their tenant and client before the single-use value is consumed, so a cross-tenant or cross-client exchange is refused without ever spending a victim's code.
See Multi-tenancy for the model in full.
Device-trust model
Every phone-originated request is a signed request: the device signs a canonical string (protocol version, method, path, device id, key id, timestamp, and a hash of the body) with its hardware-resident ES256 key. Server-side properties:
- Downgrade resistance — the protocol version is the first signed line, so a version downgrade is tamper-evident. Requests below the supported floor get
426 upgrade_required. - Clock-skew bound — the timestamp must be within a narrow window (default 60 s).
- Replay resistance — every verified signature is recorded once; a verbatim replay collides and is rejected. High-value ceremonies (registration, key rotation) additionally use one-shot nonces.
- Eager revocation — revoked keys and suspended or soft-deleted users fail the key lookup, so a suspended identity can never drive a device session.
- Make-before-break key rotation — a signature is accepted against the current key or, during an overlap, the previous key; the device is nudged to finish rotating.
App attestation
Hardware key residency is delivered transitively by app attestation — only the genuine, store-distributed Authenticator app (which generates keys in hardware) can pass. Attestation is a deployment-wide floor (require_app_attestation), deliberately with no per-client opt-out — a per-RP exemption would be a weakest-link hole.
- Android uses Play Integrity (verdicts checked server-side: app recognition, package name, signing-cert fingerprint, device integrity, and a nonce bound to the session).
- iOS uses App Attest (certificate chain pinned to Apple's root, nonce check, and a strictly-increasing monotonic counter so concurrent replays lose).
Every attestation failure returns the same opaque attestation_failed to the client; the specific subreason goes only to server logs.
Anti-enumeration design
The IdP treats membership and enrollment information as sensitive and defends it against probing. The core rule: any observable branch keyed on whether an optional input exists — an account, a PIN session, a login_hint — is a leak, so failure paths must match the success path in HTTP shape, bytes, and latency.
- Default-closed access gate — an application with zero attached groups admits nobody. Rejections raise a single opaque envelope: unknown app, unknown email, suspended user, and missing group are all indistinguishable.
- Uniform token errors — the token endpoint collapses many distinct failures into
invalid_grant; unknown client and wrong secret are bothinvalid_client. - Equivalent-latency decoys — a refused registration still performs a real outbound-email round-trip so its wall-clock latency matches the real path, and decoy PIN state mirrors the real wrong-code sequence.
- Byte-identical pages — the branded email-entry page is identical whether or not a
login_hintwas supplied.
Browser (admin console) security
The admin console is a browser SPA, protected accordingly:
- Cookie sessions, not bearer tokens — a DB-backed opaque session in an
HttpOnly,SameSite=Lax,Securecookie scoped to/v1/admin. Only a SHA-256 of the cookie token is stored, so a DB leak can't be replayed as a cookie. - CSRF — double-submit token plus an Origin/Referer gate on unsafe methods.
- Strict per-request CSP — every document gets a fresh nonce;
'unsafe-inline'is dropped fromscript-src, and Trusted Types are enforced to close DOM-XSS sinks. - Eager session revocation — every live session for a principal is killed on any credential-state change (password reset, forced temp-password change, email change), so a stale cookie can't outlive the credential it was minted under.
Machines never use cookies — they use client_credentials or SCIM tokens, which are CSRF-immune by construction.
Secrets at rest
Secrets are never stored in a form that can be replayed:
- Passwords, client secrets, API and SCIM tokens are stored hashed, not in the clear — a database leak yields nothing an attacker can present as a credential. Tokens are shown once, at creation.
- Signing keys are encrypted at rest with a key-encryption key held outside the database (with an AWS KMS custody option), as described above.
- Device keys are stored as the public key only — the private key never leaves the phone's secure hardware.
Compliance posture
SenseCrypt's design supports several regulatory positions, though compliance is a property of your whole deployment and operations, not of the software alone:
- No biometric data at rest or in transit to the service — biometric templates never leave the device, which materially reduces the scope of biometric-privacy regimes (for example BIPA-style regulations) for data you hold through SenseCrypt.
- Tamper-evident audit log — every console-governance action is linked into a per-account SHA-256 hash chain; any post-hoc edit or deletion is detectable via chain verification.
- Data minimization — dormant device keys are reaped after an inactivity window; ephemeral ceremony state is swept continuously.
- Right-to-erasure support — per-tenant crypto-shred renders a tenant's sealed material permanently unrecoverable.
- Eager credential revocation on suspend, delete, and group removal, rather than waiting for token expiry.
Treat this list as a set of building blocks. Consult your own compliance team for how they map to the specific standards you must meet.
Related
- How SenseCrypt works — the end-to-end sign-in model.
- Tokens & sessions — token contents and signing.
- Security best practices — hardening your own integration.
- Self-hosting — installing and running SenseCrypt yourself.
Self-Hosting
SenseCrypt is available as a self-managed deployment you run in your own infrastructure — provisioned and supported by our team.
Support
How to get help with SenseCrypt — the in-product contact form, the enterprise-support path for production and self-hosting, and how to report a security issue.