โ† 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

๐Ÿง‘
You
๐Ÿ–ฅ๏ธ
App server
๐Ÿ”
Login server
Sign 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, exp and nonce before trusting it
  • Send and check a nonce โ€” It stops an old ID token being replayed
  • Use the sub claim as the user ID โ€” Emails and names can change, sub does 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