Security
Built against RFC 9700, the OAuth 2.0 security best current practice from January 2025, and the March 2026 update draft.
What is not implemented
Three things are absent rather than disabled, so no configuration can bring them back:
- The implicit grant. Deprecated. It returns tokens in a URL fragment, where they land in browser history and referrer headers.
- The password grant. Deprecated. It requires your application to handle the user's password, which defeats the purpose of a redirect flow.
- PKCE method
plain. Permitted by RFC 7636, but it offers no protection when the request can be observed, which is the case it exists to cover.
What is enforced
PKCE with S256, always. There is no code path that issues a token without it, on public and confidential clients alike.
Exact redirect URI matching, compared in constant time. No wildcards, no prefixes, no normalisation. Every relaxation of this rule has produced a real account takeover somewhere.
Authorization codes live sixty seconds, are single use, and are bound to the client, the redirect URI and the PKCE challenge. Presenting one twice revokes every token issued from it, because a replay means somebody else saw it.
Refresh tokens rotate on every use. Presenting an already-rotated token revokes the whole chain. The legitimate client and a thief cannot both hold the newest token, so an old one arriving is evidence of a leak.
The iss parameter on every authorization response, per RFC 9207.
How credentials are stored
| Value | Stored as |
|---|---|
| Passwords | Argon2id, 64 MiB, t=3, p=2 |
| Refresh tokens, codes, recovery codes | SHA-256 digest only |
| Client secrets | SHA-256 digest only |
| Token signing keys | AES-256-GCM, key held outside the database |
Nothing in that table can be read back, including by us. A client secret shown once at creation is genuinely the only time it exists in readable form.
What we have not done
No independent security audit has been performed. We state this in the terms as well, because the alternative is letting you assume otherwise. When one is completed, the result is published here regardless of outcome.