โ All flows
OpenID Connect Flow
For signing users in and learning who they are
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.
See it step by step
๐ง
YouThe person using the app๐ฅ๏ธ
App serverThe website's own server๐
Login serverChecks who you are and hands out tokensSign in (openid)
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.
When to use this flow
- "Sign in with Google/Microsoft/Apple" โ Let another provider handle logins
- Single sign-on (SSO) โ One login across many apps
- Apps that need a user profile โ Name, email and picture come as claims
- Server-to-server calls โ Use Client Credentials, there is no user to identify
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
Code example
OpenID Connect example
javascript
// 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