Authentication

OpenID Connect

OAuth 2.0 authorizes access; OpenID Connect adds the identity layer that lets you log users in. It is the protocol behind the 'Sign in with...' button.

intermediate15 min readUpdated Sep 20, 2026
oidc.js
js
// oidc.js
import { Issuer } from "openid-client";

const issuer = await Issuer.discover("https://accounts.example.com");
const client = new issuer.Client({
  client_id: process.env.OIDC_CLIENT_ID,
  client_secret: process.env.OIDC_CLIENT_SECRET,
  redirect_uris: ["https://app.example.com/callback"],
  response_types: ["code"],
});

export const loginUrl = client.authorizationUrl({
  scope: "openid profile email",
  code_challenge: challenge,
  code_challenge_method: "S256",
});
Built on
OAuth 2.0
Core artifact
ID token (JWT)
Main flow
Authorization code + PKCE
Discovery
/.well-known/openid-configuration
Keys
JWKS endpoint
Optional call
UserInfo endpoint

Why it matters

What OpenID Connect adds

Real authentication

OAuth grants access to resources; OIDC proves identity. The ID token is a signed statement that the user authenticated with the provider.

One login, many apps

The same provider can sign users into every app in an organisation, which is how single sign-on works in practice.

Standard and verifiable

Discovery documents and published keys mean clients validate tokens without bespoke, provider-specific code.

The big picture

The three moving parts of a login

The provider authenticates the user, the ID token asserts who they are, and the client validates it before trusting anything.

ID token

Assert

A JWT that carries the subject, issuer, audience and expiry, signed by the provider and verified by the client.

Authorization code

Exchange

The browser receives a short-lived code, and the backend exchanges it for tokens so credentials never reach the front channel.

Validation

Trust

Check the signature against the provider's JWKS, then pin the issuer, audience and expiry before reading any claim.

At a glance

The pieces of OIDC

Discovery

A well-known document lists the endpoints and supported features.

ID token

A signed JWT describing who just logged in.

PKCE

A per-request secret that protects the code exchange for public clients.

Access token

A credential for calling APIs, separate from the identity assertion.

UserInfo

An endpoint returning profile claims for the access token.

JWKS

The provider's public keys, fetched and cached for verification.

Flow

The authorization code flow with PKCE

The browser only ever sees a code. Tokens are exchanged on the backend, where the client secret and the code verifier live.

  1. 1

    Start the login

    The client generates a code verifier and challenge, then redirects the browser to the provider's authorization endpoint with scope openid.

  2. 2

    Authenticate

    The user signs in at the provider, which is the only party that sees the password or second factor.

  3. 3

    Receive the code

    The provider redirects back to the client's registered redirect URI with a short-lived authorization code.

  4. 4

    Exchange the code

    The backend posts the code and the verifier to the token endpoint and receives an ID token, an access token and optionally a refresh token.

  5. 5

    Validate the ID token

    Verify the signature against the JWKS, then check iss, aud, exp and nonce before trusting any claim.

  6. 6

    Establish a session

    Create your own session or token from the verified subject. OIDC authenticates; it does not replace your session model.

A short history

From delegated access to federated login

  1. 2012

    OAuth 2.0 ships

    RFC 6749 standardises delegated authorization, but says nothing about identity.

    12
  2. 2014

    OpenID Connect 1.0

    A thin identity layer is layered on OAuth, defining the ID token and discovery.

    14
  3. 2015

    The ID token is a JWT

    JWT becomes the token format, and identity providers converge on it.

    15
  4. 2019

    PKCE is recommended everywhere

    OAuth 2.0 Security Best Current Practice extends PKCE to confidential clients.

    19
  5. Today

    The default for login

    "Sign in with..." buttons and enterprise SSO are OIDC in disguise.

    Today

The complete guide

OpenID Connect: Everything you need to know

OAuth is authorization, OIDC is authentication

OAuth 2.0 answers one question: may this application act on this user’s behalf? It hands out access tokens for APIs. It deliberately says nothing about who the user is. That gap caused years of confusion, and OpenID Connect is the fix.

OpenID Connect is a small identity layer on top of OAuth 2.0. It standardises the login flow, defines an ID token that asserts identity, and publishes metadata so clients can be configured without guesswork. When you click “Sign in with Google” or an enterprise SSO button, you are using OIDC.

The practical rule: if you need to know who the user is, use OIDC. If you need an app to do something on their behalf, use OAuth. Most logins need both, which is why the two are usually deployed together.

The ID token

The centrepiece of OIDC is the ID token: a JWT signed by the identity provider. It carries the claims your application needs to establish a login.

  • sub — the stable, unique identifier for the user.
  • iss — the issuer, so you know which provider signed it.
  • aud — the client id it was issued for; a token for another app must be rejected.
  • exp and iat — when it expires and when it was issued.
  • nonce — echoes the value you sent, to bind the token to this login attempt.
  • email, name, picture — profile claims, when the requested scopes allow them.

The ID token is for your client. It is not the credential you send to downstream APIs — that is the access token’s job. Keeping the two distinct avoids a common design mistake.

Discovery

Every compliant provider publishes a discovery document:

https://accounts.example.com/.well-known/openid-configuration

It lists the authorization, token, userinfo and JWKS endpoints, plus supported scopes and algorithms. Clients fetch it at startup and configure themselves, so nothing is hard-coded and provider changes are picked up automatically.

The authorization code flow with PKCE

The recommended flow keeps credentials out of the browser. The browser only ever handles a short-lived code.

  1. The client generates a random code_verifier, hashes it into a code_challenge, and redirects the browser to the provider with scope=openid.
  2. The user authenticates at the provider — the only place a password or second factor is entered.
  3. The provider redirects back with an authorization code.
  4. The backend exchanges the code, plus the verifier, for an ID token and access token.
  5. The client validates the ID token and creates a session.

PKCE (Proof Key for Code Exchange) binds the code to the client that requested it. A code intercepted in transit is useless without the verifier. Always send state to prevent CSRF and nonce to bind the ID token to the request.

Scopes and claims

Scopes decide which claims you may receive.

  • openid — required; without it the request is plain OAuth, not OIDC.
  • profile — name, picture and other profile fields.
  • email — the user’s email and whether it is verified.
  • offline_access — requests a refresh token so the app can act later.

Ask for the minimum you need. Each extra scope is more data to protect and, for some providers, more consent friction for the user.

The userinfo endpoint

The ID token often carries only the basics. For more profile data, call the userinfo endpoint with the access token:

const userinfo = await client.userinfo(tokenSet.access_token);
// { sub, name, email, email_verified, picture, ... }

Treat the sub from userinfo as authoritative and make sure it matches the sub in the ID token. Never trust an email as a stable identifier — people change them.

Validating the ID token

Validation is the step that makes the whole thing safe, and the step most often done wrong. Decoding the payload is not verification; anyone can craft a token with any claims.

Before trusting a claim:

  1. Signature — verify it with the provider’s public key from the JWKS endpoint, using an allowed algorithm.
  2. Issueriss must equal the expected provider.
  3. Audienceaud must be your client id.
  4. Expiryexp must be in the future, with a little clock skew.
  5. Nonce — must match the value you sent for this login.

Use a maintained library such as openid-client and let it do all five. Do not hand-roll JWT parsing for authentication.

Sessions after login

OIDC authenticates once, at login. It does not manage your application’s session. After validating the ID token, create your own session — a cookie or a token — keyed on sub.

Keep three things straight:

  • The ID token is for your client and proves identity.
  • The access token is for calling APIs.
  • Your session is how your app remembers the user across requests.

Mixing them leads to leaking provider tokens to the browser or using an ID token as an API credential, both of which are mistakes.

Logout

Local logout comes first: clear your session so the user is signed out of your app. That is always under your control and always reliable.

Federated logout is best-effort. You can redirect to the provider’s end-session endpoint with an id_token_hint, but not every provider honours it and the redirect may not complete. Design so that clearing your own session is sufficient, and never rely on the provider to sign the user out.

When to use OIDC

OIDC is the right choice when:

  • You want to avoid storing passwords at all.
  • You need enterprise SSO or “Sign in with…” buttons.
  • You are one of several apps that should share a login.
  • You want MFA and account recovery handled by a specialist.

It is heavier than a simple password form. For a small internal tool with a handful of users, Session Auth and Password Hashing may be simpler and entirely sufficient.

Best practices

  • Always use the authorization code flow with PKCE.
  • Send and verify both state and nonce.
  • Validate the ID token fully — signature, issuer, audience, expiry.
  • Use discovery instead of hard-coded endpoints.
  • Request the minimum scopes.
  • Keep your session separate from provider tokens.
  • Store client secrets on the backend, never in the browser.

Common mistakes

  • Treating OAuth as authentication and reading identity from an access token.
  • Decoding the ID token without verifying its signature.
  • Skipping the aud or iss checks, so a token from another app is accepted.
  • Using the implicit flow and exposing tokens in the URL.
  • Trusting the email address as the user’s primary key.
  • Sending provider tokens to your own APIs as if they were session credentials.
  • Forgetting that logout must clear your session first.

Where to go next

If you have not read it yet, start with OAuth 2.0 to understand the authorization protocol OIDC extends, and the JWT guide for the token format. Once login succeeds, the Session Auth guide shows how to keep the user signed in, and RBAC & Permissions covers what they are allowed to do.

In practice

A login in four parts

Discovery removes hard-coded URLs, the authorization URL starts the flow, and the callback exchanges and validates.

terminal
curl https://accounts.example.com/.well-known/openid-configuration
# { "issuer": "...", "authorization_endpoint": "...",
#   "token_endpoint": "...", "jwks_uri": "...", ... }

Choosing the flow

Use the authorization code flow with PKCE. Implicit and password grants leak tokens through the browser and are deprecated.

Prefer
GET /authorize?response_type=code
  &code_challenge=...&code_challenge_method=S256
Avoid
GET /authorize?response_type=token
# token returned in the URL fragment

Trusting the ID token

Validate every token against the published keys and expected values. Decoding a JWT is not the same as verifying it.

Prefer
// verify signature, iss, aud, exp, nonce
const claims = tokenSet.claims();
Avoid
const claims = JSON.parse(
  Buffer.from(idToken.split(".")[1], "base64url"),
);

Trade-offs

Should you build login on OIDC?

OIDC removes password handling and unlocks SSO, at the cost of an external dependency and a protocol worth understanding before you ship.

Strengths

  • No passwords to store

    The provider owns credentials, MFA and recovery, so your application never sees or hashes a password.

  • Single sign-on for free

    Users sign in once and reach every connected app, and enterprise customers get the SSO they expect.

  • Standard and portable

    Discovery and JWKS mean the same code works with many providers, and switching providers is configuration rather than a rewrite.

Trade-offs

  • You inherit the provider's availability

    If the identity provider is down, no one can log in, so treat it as a critical dependency and monitor it.

  • Easy to validate wrongly

    Decoding a token without checking the signature, issuer or audience is a real vulnerability that appears in production.

  • More moving parts

    Redirects, state, nonce, PKCE and token exchange are more to get right than a session cookie and a password form.

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning OpenID Connect?

Our interactive tutorial walks you through OpenID Connect step by step — with quizzes and real code you can run in the browser.