DEV Community

JWT + OAuth2 + OIDC + PKCE Complete small Guide

1. Authentication Fundamentals

Every secure application needs answers to two questions:

  • Authentication - "Who are you?"
    Example: User enters username, password, MFA. System verifies identity. Result: User is Bhargav.
  • Authorization - "What are you allowed to do?"
    Example: User: Bhargav. Permissions: READ_ORDERS, CREATE_ORDER, DELETE_ORDER.

Authentication happens first. Authorization happens after.

Authentication
       |
       v
Authorization

2. Traditional Session-Based Authentication (Stateful)

Before JWT, applications commonly used sessions.

Flow:

  • User logs in:
    Browser --> username/password --> Server
    
  • Server creates: Session ID = abc123.
    Stores in Database/Memory: abc123 -> { User: Bhargav, Role: ADMIN }.
  • Browser receives: Cookie: SESSION_ID=abc123.
  • Every request: Browser sends GET /orders with Cookie: SESSION_ID=abc123.
  • Server receives Session ID, searches session storage, finds user, allows request.

Problems with Sessions:

  1. Server maintains state - The server must remember: Session ID -> User Information.

  2. Scaling problem - Imagine multiple servers behind a load balancer:

    • User logs in on Server A, session stored there.
    • Next request goes to Server B - no session found.

    Solutions:

    • Sticky sessions
    • Shared session database

3. JWT Authentication (Stateless)

JWT solves this by putting information inside the token.
JWT: JSON Web Token - a compact, signed representation of claims between two parties.

Example: eyJhbGciOiJIUzI1Ni...

JWT vs Session

  • Session: Server stores user state: Server -> Session ID -> User Data.
  • JWT: Token contains information: JWT = Header + Payload + Signature.
    Server does not need to store session information.

4. JWT Structure

A JWT has three parts: HEADER.PAYLOAD.SIGNATURE
Example: xxxxx.yyyyy.zzzzz

Part 1: Header

Contains metadata. Example:

{
  "alg": "RS256",
  "typ": "JWT"
}

Meaning: JWT uses RSA SHA256 algorithm.

Part 2: Payload

Contains claims. Example:

{
  "sub": "12345",
  "name": "Bhargav",
  "role": "ADMIN",
  "exp": 1788888888
}

Common claims:

Claim Meaning
sub User identifier
iss Token issuer
exp Expiration time
iat Issued time
roles User permissions

Important: JWT payload is NOT encrypted. Anyone can Base64 decode it.
Therefore never store: passwords, credit cards, secrets inside JWT.

Part 3: Signature

Signature proves: "This token was created by a trusted issuer and was not modified."

Example:

Authorization Server:
  Header + Payload
        |
     Private Key
        |
      Signature

Final JWT: Header.Payload.Signature

5. Public Key and Private Key

JWT commonly uses asymmetric cryptography.

  • Private Key - Secret. Only authorization server owns it (e.g., Okta Private Key). Used for signing JWT.
  • Public Key - Can be shared (e.g., Spring Boot API uses Okta Public Key to verify).

Flow:

Okta Private Key
      |
      v
   Sign JWT
      |
      v
Spring Boot Public Key
      |
      v
   Verify JWT

6. How JWT Validation Works

Client sends: GET /orders with Authorization: Bearer JWT_TOKEN.

Spring Boot receives: HEADER.PAYLOAD.SIGNATURE and checks:

  1. Signature Validation - Spring calculates Verify(Header + Payload, Public Key). If valid: token came from trusted issuer.
  2. Expiration - Checks exp claim (e.g., "exp": 1788888888).
  3. Claims - Example: role = ADMIN allows DELETE /users.

7. Stateless Nature of JWT

JWT is called stateless because:

  • Server does not store user session.
  • Every request contains all required information.
    Example: Request + JWT = enough information.

Benefits:

  • Easy horizontal scaling
  • Works well with microservices
  • No central session storage

8. JWT Security Best Practices

Short-lived Access Tokens - Example: 5 minutes, 15 minutes, 1 hour.
Why? If stolen, attacker has limited time.

9. Refresh Token

Problem: If access token expires every 15 minutes, user must log in repeatedly.

Solution: Refresh token.

Flow:

Access Token expires
       |
       v
    Refresh Token
       |
       v
 Authorization Server
       |
       v
 New Access Token

Example: Access Token: 15 minutes, Refresh Token: days/weeks.

10. OAuth2 Relationship With JWT

Important: OAuth2 does not require JWT.

  • OAuth2 defines how to get access.
  • JWT defines how to represent the token.

Together:

OAuth2
   |
   v
Access Token
   |
   v
JWT Format

11. OAuth2 Components

  • Resource Owner - User.
  • Client - Application requesting access (e.g., Angular Application).
  • Authorization Server - Creates tokens. Examples: Okta, Google, Azure AD, Auth0.
  • Resource Server - API protecting resources (e.g., Spring Boot API).

12. OAuth2 Production Flow

Architecture: Browser - Angular - Spring Boot API - Okta

Phase 1: Backend Startup

Spring Boot starts. Configuration: issuer-uri = https://okta.com/oauth2/default.
Spring calls: GET /.well-known/openid-configuration (a REST API).
Response:

{
  "authorization_endpoint": "",
  "token_endpoint": "",
  "jwks_uri": ""
}

Spring learns: Where are public keys? How to validate tokens?

Phase 2: User Login

Browser opens https://myapp.com. Angular loads.
Angular checks: Do I have token? If no: redirect to Okta Login Page.

Phase 3: User Authentication

User enters Username, Password, MFA.
Important: Password goes to Okta, not to Angular or Spring Boot.

Phase 4: Authorization Code

Okta returns authorization_code. Example: callback?code=abc123.

Phase 5: Token Exchange

Application calls POST /token.
Okta returns: Access Token (JWT) + Refresh Token.

Phase 6: API Request

Angular sends GET /orders with Authorization: Bearer JWT.

Phase 7: Spring Validation

Spring verifies JWT signature using Okta Public Key. If valid: request allowed.

13. PKCE (Proof Key for Code Exchange)

Used mainly for: Angular, React, Mobile Apps (because they cannot store client_secret).

PKCE Memory Rule: Remember: CREATE - HIDE - PROVE

  1. Create - Angular creates code_verifier (e.g., ABC123_SECRET).
  2. Hide - Creates SHA256(code_verifier) โ†’ code_challenge.
    Sends code_challenge to Okta. Keeps code_verifier secret.
  3. Prove - Later sends authorization_code + code_verifier.
    Okta checks: SHA256(verifier) == stored challenge. If match, issue JWT.

14. Implicit Flow (Old)

Old SPA flow:

Browser
   |
   v
Authorization Server
   |
   v
JWT Token Directly
   |
   v
Angular

Problem: Token exposed in browser. Risks:

  • Browser history
  • URL leaks
  • XSS attacks

15. Token Storage Options

  • Local Storage - Example: localStorage.setItem("token", jwt)
    Pros: Simple, persistent.
    Cons: Vulnerable to XSS.

  • Session Storage - Removed when tab closes. Better than local storage but still accessible by JavaScript.

  • Secure HttpOnly Cookie - Example: Cookie: SESSION=abc123; HttpOnly; Secure; SameSite
    JavaScript cannot access it. More secure. Common with Backend For Frontend (BFF).

16. Modern Recommendation

For SPA applications: Use Authorization Code Flow + PKCE. Avoid Implicit Flow.

Summary

Concept Description
JWT A signed token containing claims that allows stateless authentication and authorization.
Session Stateful authentication where server stores user session information.
OAuth2 Framework for delegated authorization.
OIDC Authentication layer built on OAuth2.
Discovery Document Metadata endpoint that provides OAuth endpoints and public keys.
PKCE Security mechanism that prevents authorization code theft by proving that the same client started and completed authentication.

Production SPA Flow:

Angular --> PKCE Login --> Okta --> JWT --> Spring Boot --> Verify Signature --> Allow Request

Comments

No comments yet. Start the discussion.