Skip to content
v3.0.1 — MCP OAuth, organizations, passkeys, SSR adaptersChangelog
Convex Auth
Esc
↑↓navigate↵open⌘Jpreview
On this page

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 metadata
  • GET /oauth/jwks — JWKS public keys (ES256)
  • GET /oauth/authorize — Authorization-code + PKCE authorization endpoint
  • POST /oauth/token — Token exchange and refresh-token grant
  • GET /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"]
}

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_to from the query string.
  • Display the client name and requested scopes.
  • On Approve, set the convex-auth-token cookie and redirect back to redirect_to&consent_approved=true.
  • On Deny, redirect to the client’s redirect_uri with error=access_denied and the original state.

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, and email/profile claims 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_challenge are 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.

Was this page helpful?