OIDC Provider
Use convex-auth as an OAuth 2.1 / OpenID Connect provider.
convex-auth can act as a standards-compliant OAuth 2.1 / OpenID Connect provider. It exposes discovery, authorization, token, userinfo, and JWKS endpoints, and issues short-lived ES256-signed access tokens with refresh-token support.
Enable
Add oauthProvider to the convexAuth configuration in convex/auth.ts:
import { components } from "./_generated/api";
import { convexAuth } from "@vortex-api/convex-auth/convex";
const siteUrl = process.env.CONVEX_SITE_URL ?? process.env.SITE_URL ?? "";
export const auth = convexAuth({
component: components.convexAuth,
oauthProvider: {
issuer: siteUrl,
loginUrl: `${siteUrl}/oauth/consent`,
clients: [
{
clientId: process.env.OAUTH_PROVIDER_CLIENT_ID!,
name: process.env.OAUTH_PROVIDER_CLIENT_NAME ?? "Demo OIDC Client",
redirectUris: (process.env.OAUTH_PROVIDER_REDIRECT_URIS ?? "")
.split(",")
.map((uri) => uri.trim())
.filter(Boolean),
allowedScopes: (process.env.OAUTH_PROVIDER_SCOPES ?? "openid,email,profile")
.split(",")
.map((s) => s.trim())
.filter(Boolean),
},
],
},
});
Make sure http.ts registers the auth HTTP routes:
import { httpRouter } from "convex/server";
import { auth } from "./auth";
const http = httpRouter();
auth.addHttpRoutes(http);
export default http;
Configuration
The provider is configured through environment variables on your Convex deployment:
| Variable | Purpose |
|---|---|
OAUTH_PROVIDER_CLIENT_ID |
Client ID of the only known client. Matches client_id in authorization requests. |
OAUTH_PROVIDER_CLIENT_NAME |
Human-readable client name shown on the consent screen. |
OAUTH_PROVIDER_REDIRECT_URIS |
Comma-separated allowed redirect URIs (e.g. https://example.com/oauth/callback). |
OAUTH_PROVIDER_SCOPES |
Comma-separated scopes the client is allowed to request (e.g. openid,email,profile). |
OAUTH_PROVIDER_LOGIN_URL |
URL to redirect the user to for login/consent. Defaults to ${siteUrl}/oauth/consent. |
HTTP endpoints
auth.addHttpRoutes(http) wires the following routes:
GET /.well-known/openid-configuration— OIDC discovery metadataGET /oauth/jwks— JWKS public keys (ES256)GET /oauth/authorize— Authorization-code + PKCE authorization endpointPOST /oauth/token— Token exchange and refresh-token grantGET /oauth/userinfo— Userinfo endpoint (bearer access token)
Discovery
The discovery document is served from /.well-known/openid-configuration at the configured issuer URL (usually your Convex site origin):
{
"issuer": "https://your-deployment.convex.site",
"authorization_endpoint": "https://your-deployment.convex.site/oauth/authorize",
"token_endpoint": "https://your-deployment.convex.site/oauth/token",
"userinfo_endpoint": "https://your-deployment.convex.site/oauth/userinfo",
"jwks_uri": "https://your-deployment.convex.site/oauth/jwks",
"scopes_supported": ["openid", "email", "profile"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["ES256"],
"token_endpoint_auth_methods_supported": ["none"],
"code_challenge_methods_supported": ["S256"]
}
Consent and callback UI
The /oauth/authorize endpoint requires a signed-in Convex session. If consent_approved=true is not present, the provider redirects the user to loginUrl with a redirect_to query parameter pointing back to the original authorize request.
A minimal consent page should:
- Read
redirect_tofrom the query string. - Display the client name and requested scopes.
- On Approve, set the
convex-auth-tokencookie and redirect back toredirect_to&consent_approved=true. - On Deny, redirect to the client’s
redirect_uriwitherror=access_deniedand the originalstate.
examples/react/src/ConsentView.tsx and examples/react/src/OAuthCallback.tsx provide a working React reference implementation.
Tokens
The provider mints the following tokens:
- Access tokens — signed ES256 JWTs containing
sub,iss,aud,scope,azp, andemail/profileclaims based on requested scopes. They are short-lived (15 minutes by default). - Refresh tokens — opaque, single-use tokens stored on the Convex backend. They are redeemed for a new access/refresh pair.
- ID tokens — not currently issued.
Signing keys are stored in Convex and can be rotated via packages/auth/src/convex-runtime/mcp/signingRuntime.ts.
Security
The implementation supports the OAuth 2.1 authorization-code flow with the following invariants:
- PKCE S256 is required. Requests with a missing verifier, mismatched verifier, or an invalid
code_challengeare rejected. - Authorization codes are single-use. Replaying a code returns
invalid_grant. - Redirect URI must exactly match one of the configured
redirectUris. - Scope validation rejects unsupported scopes with
invalid_scope. - Client validation rejects unknown
client_id. - CORS is handled on token, userinfo, and discovery endpoints. The allowed origins are controlled by the Convex HTTP CORS layer.
Testing
packages/auth/src/convex-runtime/oauth-provider/http.test.ts covers the discovery, authorization, token, refresh, userinfo, JWKS, and negative-case flows. The examples/react/oidc-provider-test route provides a browser-based end-to-end test for the deployed provider.