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.
| Token | Audience | What it is for |
|---|---|---|
| ID token | Your application | Proves who authenticated. Verify it, read the claims, then discard it. |
| Access token | The provider's API | Calls resource endpoints. Opaque to you unless you own the API. |
| Refresh token | The token endpoint | Obtains 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.
- 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.
- 2The provider authenticates the person and redirects back with a single-use code and your state value echoed unchanged.
- 3Check the state matches what you stored. This is the CSRF defence, and skipping it is the classic OIDC bug.
- 4POST the code to the token endpoint with your client secret or PKCE verifier, and receive an ID token and an access token.
- 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.
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.
| Claim | Trust level | Use it for |
|---|---|---|
| sub | High — stable and unique per provider | The key on your user record |
| iss | High — must match the configured issuer exactly | Rejecting a token from the wrong provider |
| aud | High — must contain your client ID | Rejecting a token issued for another application |
| Medium — mutable, sometimes unverified | Display and contact, never as a primary key | |
| email_verified | Provider-dependent | Deciding whether to auto-link an existing account |
| name | Low — often absent or a placeholder | Display 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.
1curl -s https://idp.foo-corp.example/.well-known/openid-configuration | jq '{2 issuer,3 authorization_endpoint,4 token_endpoint,5 jwks_uri6}'