OpenID Connect Flow
For signing users in and learning who they are
- Who's involved
- User, app, identity provider
- Best for
- Signing users in
- Difficulty
- Moderate
1Overview
How it works
OAuth 2.0 is about permission to use an API. OpenID Connect (OIDC) is a thin layer on top that answers a different question: who just signed in?
You use the Authorization Code flow with the openid scope. Along with the access token you receive an ID token: a signed JWT (JSON Web Token) containing facts about the user.
2Use cases
When would you use this?
Sign in withβ¦
A 'Continue with Google' button on a note-taking site tells the site who you are.
Single sign-on at work
One company login opens the email, chat and HR tools without separate passwords.
Showing a profile
An app shows your name and photo straight after login, taken from the ID token.
Not the right fit? Only calling an API? Plain OAuth is enough. OIDC is for when the app needs to know who the user is.
3Interactive
Try it step by step
See it step by step
Step 1 of 6
The app sends you to sign in
The app asks the login server to identify you by adding the openid scope to its request.
4Practical advice
Do this, avoid that
Good practices
- Always validate the ID token β Check the signature,
iss,aud,expandnoncebefore trusting it - Send and check a
nonceβ It stops an old ID token being replayed - Use the
subclaim as the user ID β Emails and names can change,subdoes not - Use a certified library β Rolling your own JWT checks is easy to get wrong
Common mistakes
- Treating an access token as proof of login β Only the ID token is meant to identify the user
- Skipping signature checks β Anyone could forge a token
- Using email as the unique ID β It can change or be reused
- Sending the ID token to APIs β Use the access token for that
5Code
Code example
Show the code
// OpenID Connect sign-in (server side)
// 1. Redirect the user to the provider
const url = new URL('https://auth.provider.com/authorize');
url.searchParams.set('response_type', 'code');
url.searchParams.set('client_id', CLIENT_ID);
url.searchParams.set('redirect_uri', REDIRECT_URI);
url.searchParams.set('scope', 'openid profile email');
url.searchParams.set('state', state);
url.searchParams.set('nonce', nonce);
// 2. In the callback, swap the code for tokens
const tokens = await fetch('https://auth.provider.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code, redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID, client_secret: CLIENT_SECRET,
}),
}).then((r) => r.json());
// 3. Validate the ID token (jose library)
const { payload } = await jwtVerify(tokens.id_token, jwks, {
issuer: 'https://auth.provider.com',
audience: CLIENT_ID,
});
if (payload.nonce !== savedNonce) throw new Error('Nonce mismatch');
// payload.sub is the user\'s stable ID