Deepen PracticeOrdered learning track

OpenID Connect Authentication

Learn Java Authentication Pattern - Part 025

OpenID Connect authentication untuk Java engineers: OAuth vs OIDC, ID Token, UserInfo, discovery, nonce, state, account linking, session establishment, Spring Security OAuth2 Login, JAX-RS callback handling, logout, multi-tenant providers, dan production failure modes.

12 min read2356 words
PrevNext
Lesson 2540 lesson track23–33 Deepen Practice
#java#authentication#openid-connect#oidc+9 more

Part 025 — OpenID Connect Authentication

Target part ini: memahami dan mengimplementasikan OpenID Connect sebagai authentication protocol di Java systems. Fokusnya adalah pemisahan OAuth vs OIDC, validasi ID Token, session establishment, account linking, claim handling, Spring Security oauth2Login, custom callback handling, logout semantics, multi-tenant provider routing, dan failure modes yang sering muncul di production.

OpenID Connect adalah cara standar untuk menjawab pertanyaan:

"Siapa user ini, siapa yang mengautentikasinya, kapan autentikasi terjadi, dan bagaimana relying party bisa memverifikasi hasilnya?"

OAuth 2.0 sendiri tidak menjawab pertanyaan itu secara lengkap. OAuth memberi client access token untuk mengakses protected resource. OIDC menambahkan identity layer di atas OAuth 2.0 melalui ID Token, standard claims, discovery, dan aturan validasi authentication result.

Mental model paling ringkas:

OAuth access token  -> untuk API/resource access
OIDC ID Token      -> untuk authentication result kepada client/RP
UserInfo endpoint  -> sumber profile claims tambahan, bukan bukti login utama

Kalau aplikasi Java menerima access token lalu menganggapnya sebagai bukti login user, itu bukan OIDC yang benar. Itu hanya kebetulan bekerja sampai provider, audience, tenant, atau threat model berubah.


1. Problem yang Diselesaikan OIDC

Sebelum OIDC, banyak aplikasi membuat login eksternal seperti ini:

1. redirect user ke provider
2. provider callback membawa token
3. aplikasi decode token atau panggil profile API
4. email dari profile dianggap user identity
5. login berhasil

Masalahnya:

  • token mungkin access token untuk API, bukan authentication assertion;
  • token mungkin diterbitkan untuk client lain;
  • email mungkin belum verified;
  • user bisa berganti email;
  • beberapa provider bisa mengembalikan email yang sama;
  • callback bisa diserang login CSRF;
  • authorization code bisa ditukar oleh attacker jika redirect/client salah;
  • account linking bisa takeover jika hanya berbasis email;
  • logout lokal tidak otomatis logout dari provider;
  • tenant routing salah bisa membuat user dari organisasi lain masuk ke tenant yang salah.

OIDC memberi struktur formal:

Authorization Server / OpenID Provider authenticates End-User.
Relying Party receives ID Token.
Relying Party validates issuer, audience, signature, expiry, nonce, and claims.
Relying Party maps external subject to local account.
Relying Party creates local application session.

Dalam istilah OIDC:

IstilahMakna
OPOpenID Provider; identity provider yang mengautentikasi user.
RPRelying Party; aplikasi Java yang menerima hasil autentikasi.
End-UserHuman user yang login.
ID TokenJWT berisi claims tentang authentication event dan user subject.
Access TokenToken untuk mengakses resource server/API.
UserInfoEndpoint untuk mengambil claims tambahan.
DiscoveryMetadata endpoint agar RP tahu issuer, authorization endpoint, token endpoint, JWK Set URI, dan capability OP.

Invariant utama:

A user is not authenticated to the application until the RP validates the OIDC response and binds it to a local subject/session.

2. OAuth vs OIDC: Jangan Dicampur

OAuth menjawab:

Can this client access this resource with this token?

OIDC menjawab:

Did this OpenID Provider authenticate this End-User for this Relying Party?

Perbedaan artifact:

ArtifactDipakai olehFungsiJangan Dipakai Untuk
Authorization CodeClient/RPOne-time code untuk token exchangeBukti login langsung
Access TokenResource ServerAPI authorizationLogin session RP tanpa validasi OIDC
Refresh TokenClient/RPMendapat access token baruDibaca resource server
ID TokenClient/RPAuthentication resultAPI authorization
UserInfo responseClient/RPClaims tambahanSatu-satunya bukti login

Rule of thumb:

Login uses ID Token.
API access uses Access Token.
Long-lived continuity uses session or refresh token, depending on app type.

Salah satu kesalahan enterprise yang mahal adalah menaruh authorization role internal langsung dari ID Token ke aplikasi tanpa policy mapping. ID Token membuktikan authentication result. Authorization aplikasi tetap domain policy.


3. Authorization Code + OIDC Flow

OIDC di aplikasi server-side biasanya memakai Authorization Code Flow dengan scope=openid dan, untuk public/modern clients, PKCE.

Perhatikan pemisahan dua session:

OP session  = session user dengan provider
RP session  = session user dengan aplikasi Java

OIDC login tidak otomatis berarti semua aplikasi lokal memiliki session yang sama. SSO terjadi karena RP lain bisa redirect ke OP dan OP sudah punya session user, lalu OP menerbitkan authentication result baru untuk RP tersebut.


4. Core Data Model

Minimal model untuk OIDC login production-grade:

create table external_identity (
    id uuid primary key,
    account_id uuid not null,
    tenant_id uuid,
    provider_key varchar(100) not null,
    issuer varchar(500) not null,
    subject varchar(500) not null,
    email varchar(320),
    email_verified boolean,
    display_name varchar(300),
    linked_at timestamptz not null,
    last_login_at timestamptz,
    status varchar(40) not null,
    unique (issuer, subject),
    unique (provider_key, subject)
);

create table oidc_login_attempt (
    id uuid primary key,
    correlation_id varchar(80) not null,
    provider_key varchar(100) not null,
    tenant_hint varchar(200),
    state_hash varchar(128) not null,
    nonce_hash varchar(128) not null,
    pkce_verifier_hash varchar(128),
    redirect_uri varchar(1000) not null,
    return_to varchar(1000),
    created_at timestamptz not null,
    expires_at timestamptz not null,
    consumed_at timestamptz,
    status varchar(40) not null
);

Kenapa issuer + subject, bukan email?

Karena OIDC subject (sub) adalah identifier user dalam konteks issuer. Email adalah attribute. Email bisa berubah, bisa tidak verified, bisa direcycle oleh domain tertentu, dan bisa sama di dua issuer berbeda.

Correct identity key:

external_identity_key = (issuer, subject)

Bukan:

external_identity_key = email
external_identity_key = preferred_username
external_identity_key = name

5. ID Token Anatomy

ID Token biasanya JWT.

Contoh claims konseptual:

{
  "iss": "https://idp.example.com/realms/acme",
  "sub": "2f0d3f27-7f2c-42e3-9a2d-2f8f6c3b3b12",
  "aud": "billing-web",
  "azp": "billing-web",
  "exp": 1783051200,
  "iat": 1783050900,
  "auth_time": 1783050850,
  "nonce": "b0d5...",
  "acr": "urn:example:mfa",
  "amr": ["pwd", "otp"],
  "email": "sari@example.com",
  "email_verified": true,
  "name": "Sari Wijaya"
}

Claims yang paling penting:

ClaimFungsi
issIssuer. Harus cocok dengan provider configuration.
subStable subject identifier dalam issuer.
audClient/RP yang dituju. Harus mengandung client id aplikasi.
azpAuthorized party; penting saat multiple audience.
expExpiration. Token expired harus ditolak.
iatIssued-at. Berguna untuk skew dan audit.
nonceMengikat authorization request dengan ID Token.
auth_timeWaktu autentikasi user di OP. Penting untuk step-up.
acrAuthentication Context Class Reference. Level/context auth.
amrAuthentication Methods References. Metode auth seperti pwd, otp, hwk.
email_verifiedApakah email sudah diverifikasi oleh OP.

Rule:

ID Token claims are inputs to local authentication mapping.
They are not automatically local account truth.

6. ID Token Validation Pipeline

Validation pipeline harus deterministik.

Pseudocode:

public final class OidcAuthenticationVerifier {

    private final JwtVerifier jwtVerifier;
    private final LoginAttemptRepository attempts;
    private final ExternalIdentityRepository identities;
    private final AccountSessionService sessions;

    public LoginResult completeCallback(OidcCallback callback) {
        LoginAttempt attempt = attempts.consumeByState(callback.state())
                .orElseThrow(() -> new InvalidLoginCallbackException("Invalid login response"));

        TokenResponse tokens = exchangeAuthorizationCode(callback.code(), attempt);

        IdToken idToken = jwtVerifier.verify(tokens.idToken(), VerificationPolicy.builder()
                .issuer(attempt.provider().issuer())
                .audience(attempt.provider().clientId())
                .nonceHash(attempt.nonceHash())
                .maxClockSkewSeconds(60)
                .build());

        ExternalIdentity externalIdentity = identities.findByIssuerAndSubject(
                        idToken.issuer(),
                        idToken.subject())
                .orElseGet(() -> provisionOrLinkIdentity(idToken, attempt));

        return sessions.createBrowserSession(externalIdentity.accountId(), SessionCreationContext.from(attempt));
    }
}

Important invariant:

State protects the callback.
Nonce protects the ID Token binding.
PKCE protects the authorization code.
Issuer/audience/signature protect token origin and recipient.

Jangan menghapus salah satu karena “provider sudah trusted”. Trusted provider tetap bisa menerima request attacker jika RP tidak mengikat request dan response dengan benar.


7. Discovery and Metadata

OIDC Discovery memungkinkan RP mengambil provider metadata dari issuer.

Biasanya:

https://issuer.example.com/.well-known/openid-configuration

Metadata penting:

{
  "issuer": "https://idp.example.com/realms/acme",
  "authorization_endpoint": "https://idp.example.com/realms/acme/protocol/openid-connect/auth",
  "token_endpoint": "https://idp.example.com/realms/acme/protocol/openid-connect/token",
  "userinfo_endpoint": "https://idp.example.com/realms/acme/protocol/openid-connect/userinfo",
  "jwks_uri": "https://idp.example.com/realms/acme/protocol/openid-connect/certs",
  "response_types_supported": ["code"],
  "subject_types_supported": ["public", "pairwise"],
  "id_token_signing_alg_values_supported": ["RS256"]
}

Production rules:

  • pin allowed issuers, jangan menerima issuer dinamis dari request user;
  • cache metadata dengan TTL yang masuk akal;
  • cache JWKS, tapi refresh saat kid tidak ditemukan;
  • jangan fallback ke alg=none;
  • buat algorithm allowlist;
  • catat provider metadata version/hash untuk troubleshooting;
  • failure saat discovery/JWKS outage harus jelas: login baru bisa gagal, session existing tidak harus putus.

8. Spring Security OAuth2 Login

Spring Security menyediakan oauth2Login() untuk login via OAuth 2.0 Provider atau OIDC Provider.

Minimal config:

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    SecurityFilterChain web(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/login/**", "/assets/**").permitAll()
                        .anyRequest().authenticated())
                .oauth2Login(oauth -> oauth
                        .loginPage("/login")
                        .userInfoEndpoint(userInfo -> userInfo
                                .oidcUserService(oidcUserService())))
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .deleteCookies("APPSESSION"))
                .build();
    }

    @Bean
    OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        OidcUserService delegate = new OidcUserService();

        return request -> {
            OidcUser oidcUser = delegate.loadUser(request);
            // Map issuer + subject to local identity.
            // Do not trust email as the primary key.
            return oidcUser;
        };
    }
}

Example application config:

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: billing-web
            client-secret: ${OIDC_CLIENT_SECRET}
            scope:
              - openid
              - profile
              - email
        provider:
          keycloak:
            issuer-uri: https://idp.example.com/realms/acme

What Spring handles:

  • authorization redirect;
  • callback endpoint;
  • token exchange;
  • default ID Token validation for OIDC provider metadata;
  • user principal creation;
  • integration into SecurityContext;
  • session establishment for browser app.

What you still own:

  • local account linking;
  • tenant mapping;
  • domain policy;
  • role/authority mapping;
  • verified-email policy;
  • disabled/suspended account policy;
  • audit events;
  • logout semantics;
  • provider outage behavior;
  • privilege drift handling.

9. Mapping OIDC User to Local Principal

A safe mapper does not simply convert all claims into authorities.

public final class LocalOidcPrincipalMapper {

    public LocalPrincipal map(OidcUser oidcUser, String registrationId) {
        OidcIdToken idToken = oidcUser.getIdToken();

        String issuer = idToken.getIssuer().toString();
        String subject = idToken.getSubject();

        ExternalIdentity identity = externalIdentities
                .findActiveByIssuerAndSubject(issuer, subject)
                .orElseThrow(() -> new AuthenticationServiceException("External account is not linked"));

        Account account = accounts.findActiveById(identity.accountId())
                .orElseThrow(() -> new DisabledException("Account is not active"));

        Set<GrantedAuthority> authorities = authorizationPolicy.loadAuthorities(account.id());

        return new LocalPrincipal(
                account.id(),
                account.tenantId(),
                issuer,
                subject,
                authorities,
                idToken.getIssuedAt());
    }
}

Recommended authority policy:

OIDC claims can suggest attributes.
Local policy decides authorities.

Anti-pattern:

// Bad: every incoming role claim becomes local authority.
return oidcUser.getClaimAsStringList("roles")
        .stream()
        .map(SimpleGrantedAuthority::new)
        .toList();

Better:

// Better: map external groups through controlled local policy.
Set<String> externalGroups = oidcUser.getClaimAsStringList("groups");
return groupMappingPolicy.map(providerKey, tenantId, externalGroups);

Even better for critical apps:

External IdP authenticates.
Local authorization system authorizes.
Group claims are synchronization hints, not absolute runtime truth.

10. Account Linking and JIT Provisioning

OIDC login has two distinct operations:

Authentication: OP says this is issuer X subject Y.
Linking/provisioning: application decides which local account that external subject maps to.

Safe linking strategies:

StrategyUse CaseRisk
Admin pre-linkHigh assurance enterprise appsOperational overhead
Invite-based linkB2B SaaS onboardingInvite takeover if weak
Verified domain + verified emailWorkforce tenant onboardingDomain ownership mistakes
Self-service link after local loginConsumer account linkingSession fixation/linking CSRF
JIT provisioningLarge enterprise SSOAttribute mapping drift

Never auto-link purely by email unless you fully control the IdP/domain and require verified email.

Safer JIT provisioning flow:

Critical invariant:

A newly linked identity should start with least privilege unless an explicit trusted mapping grants more.

11. Multi-Tenant OIDC Provider Routing

Multi-tenant systems usually have multiple identity configurations.

Examples:

tenant A -> Azure Entra ID
tenant B -> Okta
tenant C -> Keycloak realm C
tenant D -> internal username/password

Provider routing can be based on:

  • tenant slug in login URL: /t/acme/login;
  • email domain discovery after user enters email;
  • invite link containing tenant context;
  • explicit organization selector;
  • dedicated custom domain per tenant.

Avoid this:

User supplies arbitrary issuer URL -> app fetches metadata -> app trusts it.

That turns login into dynamic trust injection.

Better:

tenant_id -> configured provider registration -> pinned issuer -> pinned client_id -> allowed redirect_uri

Example model:

public record TenantIdentityProvider(
        TenantId tenantId,
        String providerKey,
        URI issuer,
        String clientId,
        SecretRef clientSecret,
        Set<String> allowedEmailDomains,
        boolean jitProvisioningEnabled,
        boolean requireEmailVerified
) {}

The login attempt must store the selected provider:

login_attempt.provider_key = selected provider
callback validates state
callback uses provider_key from stored attempt, not from query parameter

12. Logout Semantics

Logout has at least three levels:

Logout TypeWhat It EndsWhat It Does Not End
Local logoutRP/application sessionOP session
OP logoutProvider sessionOther RP sessions unless logout protocols are used
Federated/single logoutMultiple RP sessionsNot always reliable in browser/network failure

For many enterprise apps, local logout is enough for application session termination but not enough for “user fully logged out of SSO”.

OIDC RP-Initiated Logout lets RP request OP logout.

Conceptual flow:

POST/GET /logout at RP
RP destroys local session
RP redirects browser to OP end_session_endpoint with id_token_hint and post_logout_redirect_uri
OP ends OP session if policy allows
OP redirects back

But production caveats:

  • not all providers implement all logout specs equally;
  • front-channel logout is browser-dependent;
  • third-party cookie restrictions affect some logout/session mechanisms;
  • logout endpoint must not become open redirect;
  • local session must be invalidated even if OP logout fails;
  • access/refresh tokens held server-side must be revoked or discarded.

Recommended invariant:

Local logout must be complete without depending on provider logout success.

13. OIDC with JAX-RS / Non-Spring Stack

For JAX-RS/Jersey services, you usually should not hand-roll full OIDC login unless you have a strong reason. Prefer a battle-tested library or push login to a BFF/gateway.

But understanding the pieces matters.

Callback resource skeleton:

@Path("/auth/oidc")
public final class OidcCallbackResource {

    private final OidcLoginService loginService;

    @GET
    @Path("/callback")
    public Response callback(
            @QueryParam("code") String code,
            @QueryParam("state") String state,
            @QueryParam("error") String error,
            @Context HttpServletRequest request) {

        if (error != null) {
            return Response.status(302)
                    .location(URI.create("/login?error=external_login_failed"))
                    .build();
        }

        LoginSession session = loginService.complete(code, state, request);

        NewCookie cookie = new NewCookie.Builder("APPSESSION")
                .value(session.id())
                .path("/")
                .httpOnly(true)
                .secure(true)
                .sameSite(NewCookie.SameSite.STRICT)
                .maxAge((int) session.maxAge().toSeconds())
                .build();

        return Response.status(302)
                .cookie(cookie)
                .location(session.safeReturnUri())
                .build();
    }
}

Do not do token validation in controller code directly. Put it behind a service with explicit policy:

public interface OidcTokenVerifier {
    VerifiedIdToken verify(String rawIdToken, ExpectedOidcContext expected);
}

The verifier owns:

  • metadata lookup;
  • JWKS cache;
  • signature validation;
  • algorithm allowlist;
  • issuer/audience validation;
  • nonce validation;
  • clock skew;
  • claim normalization;
  • error classification.

14. ID Token vs UserInfo

UserInfo can be useful when:

  • ID Token is intentionally thin;
  • profile claims are not present in ID Token;
  • provider returns updated user attributes via UserInfo;
  • app needs claims such as name, locale, picture, organization hints.

But UserInfo response is not a replacement for ID Token validation.

Correct order:

1. validate ID Token
2. identify issuer + subject
3. optionally call UserInfo with access token
4. ensure UserInfo subject matches ID Token subject
5. map claims using local policy

Important check:

if (!idToken.subject().equals(userInfo.subject())) {
    throw new AuthenticationServiceException("UserInfo subject mismatch");
}

15. Failure Modes

15.1 Access Token Used as Login Proof

Symptom:

Application decodes access token and logs user in.

Why dangerous:

  • access token may be opaque or not intended for RP;
  • audience may be API, not web client;
  • token may not contain authentication context;
  • token substitution becomes easier.

Fix:

Use OIDC ID Token for login.
Use access token only for resource access.

15.2 Missing State Validation

Symptom:

Callback accepts any code.

Impact:

  • login CSRF;
  • attacker can bind victim browser to attacker-controlled account;
  • wrong tenant/provider callback accepted.

Fix:

Generate high-entropy state.
Store it server-side or in protected cookie.
Consume it once.
Bind it to provider, redirect URI, return URL, nonce, and PKCE verifier.

15.3 Missing Nonce Validation

Symptom:

ID Token signature is valid, but nonce is ignored.

Impact:

  • replay or token injection in some flows;
  • callback not strongly bound to original auth request.

Fix:

Generate nonce per authorization request.
Store hash.
Validate ID Token nonce.
Consume login attempt.

Symptom:

If email matches an account, login succeeds.

Impact:

  • takeover via unverified email;
  • takeover through different issuer;
  • account collision across tenants.

Fix:

Primary link = issuer + subject.
Email can be an onboarding hint only under strict policy.

15.5 Trusting Roles Directly

Symptom:

roles claim becomes admin authority.

Impact:

  • external IdP misconfiguration becomes app compromise;
  • role semantics differ across tenants;
  • group explosion creates privilege drift.

Fix:

Map external groups/roles through local controlled policy.
Critical roles require explicit local grant or approval workflow.

15.6 Stale Claims

Symptom:

User removed from IdP group but existing app session remains privileged.

Fix options:

  • short app session;
  • periodic entitlement refresh;
  • webhook/SCIM deprovisioning;
  • local policy revocation version;
  • critical action step-up/re-check.

15.7 Provider Metadata Drift

Symptom:

Provider rotates signing key or changes endpoint.
Login starts failing.

Fix:

  • JWKS cache refresh on unknown kid;
  • alert on metadata fetch failure;
  • staged provider config validation;
  • runbook for cert/key rollover.

16. Production Observability

Emit structured audit events:

{
  "event_type": "OIDC_LOGIN_SUCCESS",
  "correlation_id": "auth-01J...",
  "provider_key": "acme-entra",
  "issuer": "https://login.microsoftonline.com/.../v2.0",
  "subject_hash": "sha256:...",
  "account_id": "acc_123",
  "tenant_id": "tenant_acme",
  "amr": ["pwd", "mfa"],
  "acr": "...",
  "email_verified": true,
  "ip_hash": "sha256:...",
  "user_agent_hash": "sha256:...",
  "outcome": "success"
}

Never log:

  • raw authorization code;
  • raw access token;
  • raw refresh token;
  • raw ID Token;
  • full subject if it is considered sensitive;
  • full email unless your privacy policy allows it.

Metrics:

auth_oidc_login_started_total{provider,tenant}
auth_oidc_login_success_total{provider,tenant}
auth_oidc_login_failure_total{provider,tenant,reason}
auth_oidc_callback_state_invalid_total{provider}
auth_oidc_token_exchange_latency_seconds{provider}
auth_oidc_jwks_refresh_total{provider,outcome}
auth_oidc_account_link_created_total{provider,tenant}

Operational dashboards should show:

  • provider outage;
  • invalid state spike;
  • token exchange latency;
  • JWKS refresh failure;
  • JIT provisioning spike;
  • account linking failure;
  • tenant/provider mismatch;
  • logout failure.

17. Testing Matrix

TestExpected Result
Valid OIDC callbackLocal session created.
Callback with invalid stateDeny, no token exchange.
Callback replay with same stateDeny.
ID Token wrong issuerDeny.
ID Token wrong audienceDeny.
ID Token expiredDeny.
ID Token signed with disallowed algDeny.
ID Token nonce mismatchDeny.
UserInfo sub differs from ID Token subDeny.
Email not verified but tenant requires verified emailDeny or pending onboarding.
Existing external identity disabledDeny.
Account suspended locallyDeny even if OP login succeeds.
Unknown external identity and JIT disabledDeny.
Unknown external identity and JIT enabledCreate least-privilege account.
Provider JWKS unknown kidRefresh JWKS once, then validate/deny.
Token endpoint timeoutFail login cleanly, no local session.
Return URL points outside allowed originRedirect to safe default.

18. Implementation Checklist

For every OIDC login implementation:

  • Use Authorization Code Flow.
  • Use PKCE unless your framework/provider makes a deliberate, justified exception.
  • Require scope=openid.
  • Generate and validate state.
  • Generate and validate nonce.
  • Pin issuer per provider/tenant config.
  • Validate ID Token signature using JWKS.
  • Allowlist signing algorithms.
  • Validate iss, aud, exp, iat, and nonce.
  • Validate azp when relevant.
  • Link local identity by issuer + subject.
  • Treat email as attribute, not primary key.
  • Require email_verified where email drives onboarding.
  • Keep local account status authoritative.
  • Map roles/groups through local policy.
  • Use safe return URL validation.
  • Never log raw tokens/codes.
  • Emit structured audit events.
  • Document logout semantics.
  • Test provider key rotation.
  • Test callback replay.

19. Architecture Decision Matrix

RequirementPreferred Pattern
Browser app with enterprise loginOIDC Authorization Code + local session
SPA needing strong token safetyBFF + OIDC at backend
Mobile loginAuthorization Code + PKCE + platform secure storage
Internal admin appOIDC + MFA/step-up + local authorization
B2B SaaS tenant SSOPer-tenant OIDC/SAML provider config + JIT policy
Need account lifecycle syncOIDC login + SCIM/webhook provisioning
Need API access after loginOIDC login + access token or backend token exchange
Need high assuranceOIDC + acr/amr policy + WebAuthn/passkey step-up

20. Mental Model Summary

OIDC is not “JWT login”.

OIDC is a protocol-level contract:

OP authenticates user.
RP validates authentication result.
RP maps external subject to local account.
RP creates its own session.

The most important invariants:

ID Token proves authentication to the RP.
Access Token authorizes API access.
Issuer + subject identifies external identity.
Email is an attribute.
State protects callback integrity.
Nonce binds ID Token to authentication request.
Local policy remains authoritative for local account status and authorization.

If these invariants hold, OIDC becomes a clean authentication boundary. If they are violated, OIDC becomes a complicated way to create account takeover bugs.


References

Lesson Recap

You just completed lesson 25 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.