OAuth — state, PKCE, OIDC
OAuth — state, PKCE, OIDC
OAuth 2.0 is the standard for delegating authorization without exposing user credentials. OpenID Connect sits on top to add authentication (who is this). This article covers the Authorization Code Flow with PKCE, state and nonce, scope design, and the practical differences across Google, Kakao, Naver, and GitHub OAuth.
1. About the standards
- OAuth 2.0 — RFC 6749 (2012). A framework for delegated authorization. No password sharing — tokens carry the grant.
- OpenID Connect (OIDC) — 2014. Adds an ID Token and a userinfo endpoint on top of OAuth 2.0 to standardize authentication. The ID Token is a JWT.
- PKCE — RFC 7636 (2015). An extension that blocks authorization code interception for public clients (SPA, mobile).
- OAuth 2.1 — a consolidation in progress. It bundles safer defaults (mandatory PKCE, removal of implicit flow, etc.).
2. Authorization Code + PKCE flow
- The client generates a
code_verifier(random string).code_challenge = base64url(sha256(verifier)). - Redirect to
/authorize?response_type=code&client_id=...&redirect_uri=...&scope=...&state=...&code_challenge=...&code_challenge_method=S256. - The user consents.
- The IdP returns to
redirect_uri?code=...&state=.... - The client compares
stateagainst the previously stored value (CSRF check). - The client posts
code+code_verifierto/token. - The IdP confirms that
code_challengematchescode_verifierand issues tokens.
3. The state parameter
state is centrally about CSRF protection. It blocks the login-CSRF scenario where an attacker's authorization code is redirected to a victim, who then logs into the attacker's account.
- The client creates a random nonce-like value and stores it in the session.
- It is sent on
/authorizeasstate. - The callback compares the received
stateagainst the stored one. - Reject on mismatch.
The state value should be sufficiently long random data with negligible collision probability.
4. nonce (OIDC)
A value carried inside the OIDC ID Token. Its purpose is replay prevention for the ID Token itself.
- The client sends a nonce on the auth request.
- The IdP returns the same nonce as a claim in the ID Token.
- The client checks that the nonce matches.
state and nonce serve different purposes; both are needed.
5. What PKCE accomplishes
Code interception attacks come from the fact that mobile and SPA clients cannot safely store a client secret. PKCE lets only the same-origin client redeem the token without a client secret — even if the code is intercepted, the token exchange fails without code_verifier.
PKCE was optional for SPA and mobile under OAuth 2.0; under OAuth 2.1 it becomes effectively mandatory for all clients.
6. Deprecated flows
Implicit Flow — response_type=token returns the access token in the URL fragment directly. No code step → the token leaks to browser history and logs. No longer recommended; removed in the OAuth 2.1 draft.
Resource Owner Password Credentials (ROPC) — the user types a password directly into the client. It contradicts OAuth's core motivation (do not share passwords). Deprecated.
Device Authorization Grant — RFC 8628 (2019). A flow where a separate device handles authentication for input-constrained devices (TV, IoT). device_code and user_code are the core ideas.
7. Scope design
- Standard OIDC scopes —
openid,profile,email,address,phone. - Provider-specific — Google often uses URL-shaped scopes like
https://www.googleapis.com/auth/calendar.readonly. - Your own API — your domain plus a permission name (
api.read:posts).
Recommendations:
- Principle of least privilege. Do not request everything up front; ask for incremental authorization when needed.
- Make the consent screen clear to the user.
- Verify again on the server. Do not trust the client token's scope.
8. Provider-specific differences
Google — fully OIDC standard. Discovery document at https://accounts.google.com/.well-known/openid-configuration. ID Token is RS256, JWKS rotation. Incremental authorization is supported. Consent can be skipped on subsequent requests after the first approval (force it with prompt=consent).
Kakao — OAuth 2.0 based. OIDC is also supported (added around 2022, ID Token issuance). Consent items must be configured separately in the Kakao Developers console for them to appear to the user. Some fields (such as email) may come back empty if the user has hidden them.
Naver — OAuth 2.0. Does not provide a standard OIDC ID Token. Fetch user info separately from the userinfo API (/v1/nid/me). Common where the audience is Korean users.
GitHub — OAuth 2.0 (OIDC is limited to specific contexts like GitHub Actions OIDC). General login is OAuth 2.0, not OIDC. User info comes from GET /user. Scopes are simple (read:user, repo) but powerful — repo grants access to all repositories.
Apple Sign in — OIDC standard. Uses ID Token. Users can hide their email (private relay). The user's name is included only in the first login response — the backend has to save it then.
9. Safe defaults
- One auth flow: Authorization Code + PKCE.
redirect_uriis only an exact match against pre-registered values (wildcards are dangerous).- Do not skip state and nonce verification.
- Separate access and refresh tokens; short access TTL (01-jwt-rotation).
- Tokens go into httpOnly cookies or a secure keystore.
- The backend verifies signed ID Token claims —
iss,aud,exp,nonce.
10. Common pitfalls
Missing or fixed state — CSRF protection effectively disappears. Use a fresh value per request.
Loose redirect_uri — wildcards or prefix matching are dangerous. Only exact matches.
Public clients with secrets — client secrets in mobile and SPA leak. Replace with PKCE.
Skipping OIDC aud validation — accepting an ID Token issued for a different client leads to token-confusion incidents.
Drift in user info responses — providers may change response key names or null handling. Defensive code.
Trusting email — email from providers like GitHub may be unverified. Add your own email verification flow.
What logout means — ending our session and ending the IdP session are different things. RP-Initiated Logout is a separate OIDC specification.
Concurrent callbacks — if a user starts OAuth from multiple tabs, states get mixed. Separate the storage keys for state.
Closing thoughts
The standard flow of OAuth 2.0 + PKCE + state + nonce is the foundation of safe defaults. A better place is to go further down to OIDC ID Token verification (iss, aud, exp, nonce). Small provider-specific differences (Naver's missing OIDC, GitHub's unverified emails) are typical sources of mistakes, so an in-house email verification flow often rides along as backup.
Next
- rate-limit-redis
- input-validation-zod
We refer to RFC 6749 OAuth 2.0, RFC 7636 PKCE, RFC 9700 OAuth 2.0 BCP, OpenID Connect Core, Google Identity, Kakao Login, Naver Login API, and GitHub OAuth.