OpenID Connect defines how an application actually obtains an ID token, and the flow it steers you toward is the authorization code flow. It is a redirect dance with a deliberate split between what travels through the browser and what does not.
The steps
- The application (the relying party) redirects the user to the identity provider's authorization endpoint, with parameters:
client_id, aredirect_uri,scope=openid(plus any others),response_type=code, astatevalue, and anonce. - The user authenticates at the provider and consents. The application is not involved in that; it never sees the credentials.
- The provider redirects the browser back to the
redirect_uriwith a short-lived authorization code in the URL. - The application takes that code and, over a direct back-channel call to the token endpoint, exchanges it (authenticating itself with a client secret or ) for an ID token and usually an access token.
- The application validates the ID token: its signature, issuer, audience, expiry, and that the
noncematches the one it sent.
Why the code, and not the token, comes back
The reason for the two-step exchange is that the authorization code is nearly useless on its own. Even if someone intercepts it from the URL, they cannot redeem it without the back-channel call and the client's authentication. Contrast that with the old implicit flow, which returned the token directly in the browser URL, where it could leak through history, referrers, or logs. Implicit is now discouraged for exactly that reason, and the code flow is preferred everywhere.
state, nonce, and PKCE
Three parameters do the security heavy lifting. state ties the response back to the request the application started, defeating cross-site request forgery on the callback. nonce is echoed inside the ID token so the application can confirm the token answers its request and is not a replay. PKCE adds a one-time proof so that even a public client with no secret (a single-page or mobile app) can prove it is the same party that began the flow. Together they make the redirect dance safe to run in a browser, which is why the authorization code flow with PKCE is the modern default.
state and nonce defend different attacks, and one is not two
They are frequently implemented as if either satisfies the requirement. They do not overlap.
state binds the redirect to the browser session that started it. Without it, an attacker can complete their own authorisation and have the victim's browser deliver the resulting code — the victim ends up logged into the attacker's account, which is quiet, confusing, and worse than it sounds when the account is then used to collect what the victim uploads.
nonce binds the ID token to that same request, so a token issued for one login cannot be replayed into another.
Implementing one and assuming the other is covered leaves a whole attack open, and neither failure produces an error — the flow completes, the user is signed in, and the only sign is that they are signed in as somebody else.