Security
This page describes what mcpauth actually does, not a compliance checklist. Everything below is true of the code running in production today — where we haven't done something yet, we say so, in the last section.
Tokens are opaque, not JWTs — revocation is immediate
mcpauth's access and refresh tokens aren't JWTs. They're high-entropy random strings, and the database stores only a SHA-256 hash of each one — never the token itself. When you revoke a token, we set a revokedAt timestamp in Postgres, and the very next introspection check sees it and rejects the token.
This matters because it's a real tradeoff most JWT-based systems don't get for free: a stateless JWT is valid until it naturally expires unless you build and maintain a separate revocation blocklist. mcpauth doesn't need one — the database lookup is the revocation check.
Nothing sensitive is stored in plaintext
Every credential-shaped value in the schema is hashed (SHA-256) at rest, so a database read alone never yields a usable secret:
- Access tokens and refresh tokens
- OAuth client secrets (for confidential clients)
- Project registration secrets
- Authorization codes
- Dashboard session tokens
OAuth 2.1 hardening, specifically
- PKCE is mandatory, not optional. Every authorization request must include a
code_challengewithS256; requests without one are rejected before a code is ever issued. - redirect_uri must match exactly— no prefix or partial matching against a client's registered URIs, which is a common source of open-redirect vulnerabilities in looser OAuth implementations.
- Authorization codes are single-use, and reuse is treated as a signal of interception: if a code is replayed, every active token issued to that client is revoked immediately as a precaution, not just the one request rejected.
- Secret comparisons run in constant time (Node's
timingSafeEqual), so verifying a client secret or hashed credential can't leak information through response-time differences.
Infrastructure
The authorization server runs on Vercel, with Postgres on Railway. All traffic is HTTPS. Database credentials and signing secrets are held as environment variables, never committed to source control or exposed to the client.
What we haven't done yet
We'd rather say this plainly than let silence imply otherwise: mcpauth has not been through a formal third-party security audit or penetration test, and we don't hold a SOC 2 or similar compliance certification. Everything above is accurate about the code as written, but it hasn't been independently verified by an outside party yet. If that's a hard requirement for your use case today, we're not the right fit yet — we'd genuinely rather you know that upfront.
Reporting a vulnerability
Please report security issues privately via GitHub Security Advisories on the SDK repository, rather than a public issue.