Blog

OpenID Connect (OIDC) Example

Key Takeaways

  • OpenID Connect adds authentication to OAuth 2.0; it doesn't replace OAuth's authorization framework.
  • An ID token tells the client about the authenticated user. An access token authorizes requests to its intended API; don't substitute an ID token.
  • Start new integrations with Authorization Code Flow and PKCE. Public clients must use PKCE under current OAuth security guidance.
  • Validate ID tokens before trusting their claims. Call UserInfo with an appropriate access token when you need additional identity claims.

OpenID Connect (OIDC) is an authentication layer built on OAuth 2.0. In this OpenID Connect (OIDC) example, your application redirects a user to an identity provider, exchanges an authorization code for tokens using PKCE, and validates the ID token before signing the user in.

If you need a refresher on clients, authorization servers, and resource servers, start with how OAuth 2.0 works.

OpenID Connect vs OAuth 2.0

Authentication answers “Who signed in?” Authorization answers “What may this client access?” OAuth 2.0 defines a framework for obtaining access to protected resources, but an access token alone isn't a standardized login assertion for the client. OIDC adds that assertion through the ID token.

ArtifactWho consumes it?Purpose
ID tokenThe relying-party applicationValidate the user's authentication and identify the subject.
Access tokenThe intended resource serverAuthorize access to protected resources, including UserInfo when applicable.
Refresh tokenThe authorization server's token endpointRequest new access tokens without repeating the interactive login flow, when permitted.

These token roles are separate even when tokens arrive in the same response. An ID token authenticates the user to the client; it does not authenticate the client application to the token endpoint.

Access tokens can be opaque strings or JWTs. A JWT-shaped access token still isn't an OIDC ID token, and a token intended for one API isn't automatically valid at another. The JWT access-token profile defines a separate token type and validation rules.

OpenID Connect Example: Authorization Code Flow with PKCE

Consider an application that delegates login to a fictional provider:

  • OpenID Provider (OP): the identity service at https://issuer.example.com, which authenticates the user and issues tokens.
  • Relying Party (RP): your application at https://app.example.com, which relies on the OP's authentication result.

These are the standard OIDC names for the identity provider and client application. The domains and endpoint paths below are illustrative, not a running service.

1. Register the client and discover its endpoints

Register your application with the provider, obtain a client_id, and register the exact callback URL, here https://app.example.com/callback. Configure the correct client type: a browser-only SPA or native app is a public client and cannot safely keep a shared client secret.

For a provider supporting OIDC Discovery, fetch its configuration document. For this example's issuer, the location is https://issuer.example.com/.well-known/openid-configuration. Important metadata includes:

  • issuer: the expected issuer identifier.
  • authorization_endpoint and token_endpoint: where login starts and codes are exchanged.
  • jwks_uri: the JSON Web Key Set location containing the provider's signing keys.
  • userinfo_endpoint: the UserInfo URL, when provided.

Use metadata from your configured, trusted issuer rather than inventing endpoint paths or accepting an issuer supplied by an untrusted token. Provider registration and SDK behavior remain provider-specific.

2. Start the authorization request

PKCE, short for Proof Key for Code Exchange, binds code redemption to the client instance that started the request. The client generates a fresh random code_verifier, keeps it for the transaction, and sends a derived code_challenge with code_challenge_method=S256. Someone who intercepts only the authorization code cannot redeem it without the verifier.

The following is an illustrative HTTP fragment, not a complete login implementation. Replace YOUR_CLIENT_ID, STATE, and NONCE; generate fresh values for every transaction. The challenge and verifier shown here are the published RFC 7636 test pair, not production values to reuse.

# Illustrative authorization request; replace all example values.
GET /authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile%20email&state=STATE&nonce=NONCE&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1
Host: issuer.example.com

The parameters have different jobs:

  • response_type=code: requests an authorization code rather than tokens in the redirect.
  • scope=openid profile email: openid makes this an OIDC request. profile and email request groups of identity claims; they don't guarantee every claim will be returned.
  • state: correlates the callback with the initiating browser transaction and provides cross-site request forgery (CSRF) protection when securely bound to that transaction.
  • nonce: binds the returned ID token to this authentication request and helps prevent replay.

Request only the claims and API permissions you need. Consent and provider policy affect what is granted; requesting an email address doesn't guarantee that the account has one.

3. Authenticate the user and receive the callback

The OP authenticates the user and obtains any necessary authorization or consent. With the usual query response mode for code flow, it redirects the browser to a callback shaped like this:

# Illustrative callback URL.
https://app.example.com/callback?code=AUTHORIZATION_CODE&state=STATE

Check that the returned state matches the pending transaction. Handle authorization errors, reject missing or mismatched state in this example's design, and don't treat possession of a code as a completed login.

4. Exchange the code for tokens

The RP sends the code and its original verifier directly to the token endpoint. This fragment shows a public client, so it contains no client secret:

# Illustrative token request; replace the code and client configuration.
POST /token HTTP/1.1
Host: issuer.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=YOUR_CLIENT_ID&code=AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

A confidential backend client also authenticates using its registered token-endpoint authentication method. PKCE doesn't replace that authentication. A successful standard OIDC code exchange returns an ID token and an access token; refresh-token issuance is conditional, not guaranteed.

The RP then validates the ID token, establishes the authenticated subject, and creates its application session. It presents the access token only to an intended resource server, which may be UserInfo or another authorized API—not necessarily an API operated by the identity provider.

Front channel: browser redirects
RP --> Browser --> OP authorization endpoint: request + challenge
OP --> Browser --> RP callback: code + state

Direct HTTPS: server-to-server for a backend RP
RP --> OP token endpoint: code + verifier (+ client authentication)
OP --> RP: ID token + access token; refresh token if issued
RP --> Resource server / UserInfo: appropriate access token

For a browser-only SPA, the direct token request runs in the browser rather than on a backend, so the provider must support the required cross-origin access. Code flow doesn't magically move SPA tokens out of JavaScript.

Authorization Code, Implicit, and Hybrid Flows

OIDC defines all three flows, but they aren't equally good defaults for new applications. Current OAuth security guidance requires PKCE for public clients and recommends it for confidential clients.

FlowWhere the response arrivesGuidance
Authorization Code: codeCode through the browser redirect; tokens from the token endpoint.Use with PKCE as the default for new integrations.
Implicit: id_token or id_token tokenTokens through the browser front channel, normally in the URL fragment.Legacy integration pattern; prefer code with PKCE for new applications.
Hybrid: for example, code id_tokenA code and some tokens through the front channel; additional tokens from the token endpoint.Use only when a specific integration or security profile requires it.

The security guidance specifically discourages authorization responses that expose access tokens through the front channel. ID-token-only Implicit Flow remains specified; that doesn't make it the recommended general-purpose SPA login pattern. Hybrid Flow also remains available for specialized integrations.

OIDC ID Tokens: Claims and Validation

An ID token is a JSON Web Token (JWT) containing claims—statements about the authentication event and subject. This is an illustrative decoded payload, not a complete signed token or a token you can use to log in. The timestamps and identity details are fictional.

{
  "iss": "https://issuer.example.com",
  "sub": "user-123456",
  "aud": "YOUR_CLIENT_ID",
  "exp": 2000003600,
  "iat": 2000000000,
  "nonce": "NONCE",
  "name": "Alex Johnson",
  "given_name": "Alex",
  "family_name": "Johnson",
  "birthdate": "1990-11-15",
  "email": "alex@example.com",
  "picture": "https://profiles.example.com/alex.jpg"
}

The required core claims include iss (issuer), sub (subject), aud (audience), exp (expiration), and iat (issuance time). Additional claims can be required by the request or flow; profile fields such as name, birthdate, email, and picture are optional.

Validate before establishing a session

Use a maintained OIDC client library, configured for your issuer and client ID. Decoding a JWT payload only reads its contents; it doesn't establish trust. Your validation configuration should cover:

  • Signature and algorithm: verify the signature with trusted issuer keys and an explicitly allowed algorithm; don't let an untrusted token choose your security policy.
  • Issuer: require an exact match between iss and the configured issuer.
  • Audience: require aud to contain your client ID and apply the applicable rules for additional audiences and azp, the authorized-party claim.
  • Time: reject expired tokens and apply appropriate issuance-time and clock-skew checks.
  • Request binding: if you sent a nonce, require the same nonce in the ID token and prevent transaction reuse.
  • Other requested guarantees: enforce applicable checks such as authentication age when your application requests them.

This is a configuration checklist, not a replacement for the complete OIDC validation procedure. Follow JWT validation best practices alongside your provider's requirements.

Store the external identity using the (iss, sub) pair. A subject identifier is scoped to its issuer, not globally unique; email addresses aren't a safe replacement identity key.

The OIDC UserInfo Endpoint

UserInfo is a protected endpoint that can return authorized identity claims beyond those included in an ID token. Discover its URL from userinfo_endpoint and send an access token authorized for that endpoint—not the ID token.

# Illustrative UserInfo request; use the discovered endpoint.
GET /userinfo HTTP/1.1
Host: issuer.example.com
Authorization: Bearer ACCESS_TOKEN

Bearer access tokens belong in the Authorization header, not in application logs or copied into arbitrary URLs. The same rule applies when calling other protected Web APIs.

UserInfo must return sub. Verify that it exactly matches the subject in the validated ID token before using the response; discard the claims if it doesn't. UserInfo isn't a general user-directory lookup endpoint.

Don't call it automatically if the validated ID token already contains everything you need. It also isn't guaranteed to return a richer profile: for example, Microsoft's UserInfo documentation explains that its ID token contains a superset of the UserInfo data.

Common Mistakes

  • Shipping a client secret in a SPA: a secret distributed to browsers isn't confidential. Use the public-client registration and PKCE.
  • Equating login with API permission: authenticate the user with OIDC, then enforce the API's authorization rules separately.
  • Assuming PKCE solves browser security: it protects code redemption, not tokens stolen through malicious JavaScript. Protect against cross-site scripting and avoid logging tokens or verifiers.
  • Treating the ID token as your session system: define your application's session lifecycle. For backend applications, prefer keeping provider tokens server-side and using a Secure, HttpOnly session cookie with an appropriate SameSite policy.

Use HTTPS throughout production login and token exchanges; see SSL vs TLS for the transport-security distinction. For framework-level integration, see the Java web authentication example.

FAQ

What is the difference between OpenID Connect and OAuth 2.0?

OAuth 2.0 is an authorization framework for accessing protected resources. OpenID Connect adds standardized user authentication through ID tokens, so use it when your application needs login rather than API access alone.

What is the difference between an OIDC ID token and an access token?

An ID token tells the client about the authenticated user; an access token authorizes requests to an intended resource server. Validate the ID token for login, and don't send it as an API bearer credential.

Should a SPA use Implicit Flow or Authorization Code Flow with PKCE?

Use Authorization Code Flow with PKCE for a new SPA integration. It avoids returning tokens in the authorization redirect and prevents an intercepted code from being redeemed without the verifier, but it doesn't eliminate browser token-storage or cross-site scripting risks.

What must an OIDC client validate in an ID token?

Validate the signature with trusted keys and an allowed algorithm, the expected issuer, the client audience, expiration, and other applicable time and request checks. If the authorization request included a nonce, require an exact nonce match; use an OIDC library to apply the full validation rules.

Can I use OIDC without implementing OAuth 2.0 separately?

Yes. An OIDC client library can handle the underlying OAuth interactions for you, but OIDC itself still builds on OAuth 2.0 rather than replacing it.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. More about me