Skip to content

Concepts

What is OIDC?

OpenID Connect is a thin identity layer over OAuth 2.0. OAuth was designed to grant access to resources; OIDC adds a signed token that says who the person is. If SAML feels like a document exchange, OIDC feels like an API — which is why almost every provider built in the last decade speaks it.

OAuth first, then identity

OAuth 2.0 solves delegation: letting one application act on a resource owner's behalf without holding their password. It deliberately says nothing about who that person is, which meant every implementation invented its own way of asking — and most of them got it wrong.

OIDC standardises the answer. It keeps the OAuth authorization code flow intact and adds one thing: an ID token, a signed JWT containing claims about the person who just authenticated. Same redirects, same code exchange, one extra token with a defined meaning.

Three tokens, three jobs

Confusing these is the most common source of real security bugs in OIDC integrations, because two of them look similar and only one is meant for you.

TokenAudienceWhat it is for
ID tokenYour applicationProves who authenticated. Verify it, read the claims, then discard it.
Access tokenThe provider's APICalls resource endpoints. Opaque to you unless you own the API.
Refresh tokenThe token endpointObtains a new access token. Long-lived, so treat it as a credential.

An ID token is not a session. It proves an authentication happened at a moment in time. Read it once, create your own session, and never accept an ID token from a browser as proof of an ongoing sign-in.

The authorization code flow

This is the only flow to use for a server-side application, and with PKCE it is also the right one for a single-page app or a mobile client. The implicit flow is deprecated and should not appear in new code.

  1. 1Redirect the browser to the provider's authorize endpoint with your client ID, redirect URI, scopes, a state value and, for public clients, a PKCE challenge.
  2. 2The provider authenticates the person and redirects back with a single-use code and your state value echoed unchanged.
  3. 3Check the state matches what you stored. This is the CSRF defence, and skipping it is the classic OIDC bug.
  4. 4POST the code to the token endpoint with your client secret or PKCE verifier, and receive an ID token and an access token.
  5. 5Verify the ID token, read the claims, and create your own session.

Verifying an ID token

A JWT is signed, not encrypted — anyone can read it, so the signature is the entire security boundary. Verification is more than checking that the signature parses.

Paycux performs all of these checks before your callback runs. If you are implementing OIDC directly against a provider, this is the list you cannot skip.

id-token-claims.json
1{
2 "iss": "https://idp.foo-corp.example",
3 "aud": "client_01HQZX8N4T",
4 "sub": "00u1a2b3c4d5e6f7g8h9",
5 "email": "avery@foo-corp.example",
6 "email_verified": true,
7 "name": "Avery Lindqvist",
8 "iat": 1776412364,
9 "exp": 1776415964,
10 "nonce": "n-0S6_WzA2Mj"
11}

Which claims to trust

Not every claim carries the same weight. The subject is the only one guaranteed stable; everything else is a convenience that a provider may or may not populate accurately.

ClaimTrust levelUse it for
subHigh — stable and unique per providerThe key on your user record
issHigh — must match the configured issuer exactlyRejecting a token from the wrong provider
audHigh — must contain your client IDRejecting a token issued for another application
emailMedium — mutable, sometimes unverifiedDisplay and contact, never as a primary key
email_verifiedProvider-dependentDeciding whether to auto-link an existing account
nameLow — often absent or a placeholderDisplay only, with a fallback

Discovery and key rotation

Every compliant provider publishes a discovery document at a well-known path, listing its endpoints and the URL of its signing keys. Reading it at runtime rather than hardcoding endpoints is what lets a provider rotate keys without breaking you.

Cache the key set, but honour its cache headers and refetch when a token arrives signed with a key ID you do not recognise. A provider rotating keys on a Saturday is the usual cause of an integration that worked on Friday and does not on Monday.

terminal
1curl -s https://idp.foo-corp.example/.well-known/openid-configuration | jq '{
2 issuer,
3 authorization_endpoint,
4 token_endpoint,
5 jwks_uri
6}'