Skip to content

Chapter 9 of 204 min readBy Deepak GuptaPublished

Building an OAuth 2.0 and OpenID Connect Server

Build your authorization server to the OAuth 2.0 Security Best Current Practice (RFC 9700, January 2025) from day one: authorization code with PKCE for every client, exact redirect URI matching, no implicit grant, and no password grant. (RFC 9700) That is effectively OAuth 2.1, which is still an IETF draft: revision 17 was published on October 31, 2026. (IETF Datatracker)

Use a certified OpenID Connect server library and pass the OpenID Foundation conformance suite before launch. Do not hand-roll the protocol.

The endpoints you will build

Endpoint Standard Purpose
/.well-known/openid-configuration OpenID Connect Discovery, RFC 8414 Publishes every endpoint, supported algorithm, and feature
/authorize RFC 6749, OIDC Core Authenticates the user, gets consent, returns a code
/token RFC 6749 Exchanges code, refresh token, or client credentials for tokens
/userinfo OIDC Core Returns claims for the access token's user
/jwks.json RFC 7517 Public keys for verifying your tokens
/revoke RFC 7009 Lets clients revoke tokens on sign-out
/introspect RFC 7662 Lets resource servers check opaque tokens
/par RFC 9126 Pushed authorization requests, so parameters are not exposed in the browser URL
/device_authorization RFC 8628 Sign-in for TVs and CLIs

Authorization code with PKCE, step by step

  1. Client creates a random code_verifier and sends code_challenge = BASE64URL(SHA256(code_verifier)) with code_challenge_method=S256, plus client_id, redirect_uri, scope, state, and nonce.
  2. Server validates client_id, then compares redirect_uri to the registered value by exact string match. On mismatch, show an error page; never redirect.
  3. Server authenticates the user (your login flow, with step-up if acr_values or max_age require it) and records consent for third-party clients.
  4. Server issues a single-use authorization code valid for about a minute, bound to client, redirect URI, PKCE challenge, user, and nonce. It returns code, state, and iss (RFC 9207, which defends against mix-up attacks).
  5. Client posts the code and code_verifier to /token, authenticating itself if confidential.
  6. Server checks the verifier against the stored challenge, burns the code, and issues tokens. If a code is used twice, revoke every token issued from it.

RFC 9700 requires servers to reject a code_verifier when no challenge was sent, which blocks PKCE downgrade. Accept only S256.

Client authentication

Method Use for
None (public client) + PKCE Mobile apps, SPAs through a backend-for-frontend
private_key_jwt Preferred for confidential clients: no shared secret to leak
mTLS (RFC 8705) High-assurance and financial-grade APIs
client_secret_basic Acceptable for simple server apps; rotate secrets, store them hashed

ID tokens

An ID token tells the client who signed in. Include iss, sub, aud, exp, iat, auth_time, nonce, acr, and amr. Make sub a stable, opaque identifier per user, and consider pairwise subjects (different sub per client) for consumer privacy.

Scopes and consent

  • Keep scopes coarse enough to understand, fine enough to limit damage: orders:read, not api.
  • Record consent per user, client, and scope, with a way to review and revoke it.
  • First-party clients can skip the consent screen; third-party clients never should.

Machine and agent access

  • Client credentials for service-to-service calls, with short-lived tokens and narrow scopes.
  • Token exchange (RFC 8693) when one service or AI agent acts on behalf of a user, so tokens show both the user and the actor.
  • Protected resource metadata (RFC 9728) and dynamic client registration if you expect AI agents and MCP clients to discover and connect to your APIs.
Warning

Common mistakes

  • Wildcard or prefix redirect URI matching.
  • Supporting the implicit or password grant "for legacy clients."
  • Authorization codes that can be used twice, or live for minutes.
  • Access tokens without audience restriction that every API accepts.

Key takeaways

  • Build to RFC 9700 from day one: PKCE with S256 for every client, exact redirect matching, no implicit or password grant.
  • Make authorization codes single use and short lived, and return iss to stop mix-up attacks.
  • Prefer private_key_jwt or mTLS for confidential clients.
  • Plan for agents now: token exchange, protected resource metadata, and dynamic client registration.