Dung (Donny) Nguyen

Senior Software Engineer

JWT & Intro to OAuth2 / OpenID Connect

Once you’ve grasped authentication and authorization, the next question is: how do modern, stateless, distributed systems actually carry identity across requests and services? The answer usually involves three technologies people constantly mix up — JWT, OAuth2, and OpenID Connect.

They are not competitors. They sit at different layers:

Let’s untangle them.


JWT — JSON Web Token

A JWT is a compact, URL-safe, digitally signed string that carries a set of claims. It’s the format most commonly used to represent identity and permissions in stateless systems.

A JWT has three parts, separated by dots — header.payload.signature:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiQURNSU4ifQ.SflKx...

1. Header

Describes the token type and the signing algorithm.

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

2. Payload

Contains the claims — statements about the user and metadata. Standard (registered) claims include:

{
  "sub": "1234567890",     // subject (user id)
  "name": "Alice",
  "role": "ADMIN",
  "iat": 1735689600,        // issued at
  "exp": 1735693200         // expiry
}

⚠️ The payload is Base64URL-encoded, not encrypted. Anyone can decode and read it. Never put passwords or secrets in a JWT.

3. Signature

Guarantees the token wasn’t tampered with. The server computes it from the header, payload, and a secret (or private key):

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

If a single character in the payload changes, the signature no longer matches and the token is rejected.

The Trade-off

Because JWTs are stateless, they’re hard to revoke before they expire. Mitigations:


OAuth2 — Delegated Authorization

OAuth2 solves a specific problem: how can an application access resources on a user’s behalf without ever seeing the user’s password?

Think “Sign in with Google” granting a third-party app access to your calendar. You never give the app your Google password — Google issues it a scoped token instead.

The Four Roles

The Authorization Code Flow

The most common and secure flow for web and mobile apps:

  1. The user clicks “Sign in with Google” in the client app.
  2. The client redirects the user to the authorization server.
  3. The user authenticates and consents to the requested scopes.
  4. The authorization server redirects back with a short-lived authorization code.
  5. The client exchanges that code (plus its secret) for an access token — this happens server-to-server.
  6. The client calls the resource server, sending Authorization: Bearer <access token>.

Modern public clients (SPAs, mobile apps) add PKCE (Proof Key for Code Exchange) to this flow to protect the code exchange without a client secret.

Scopes

OAuth2 access is scoped — a token grants only what was requested and consented to, e.g. read:calendar, write:email. This enforces the principle of least privilege.

The Key Limitation

OAuth2 was designed for authorization, not authentication. An access token tells the resource server “this bearer may access these scopes” — it says nothing reliable about who the user is. That gap is exactly what OpenID Connect fills.


OpenID Connect (OIDC) — Authentication on Top of OAuth2

OpenID Connect is a thin identity layer built on top of OAuth2. It standardizes authentication by adding one crucial thing: the ID token.

The ID token is always a JWT and contains identity claims:

{
  "iss": "https://accounts.google.com",   // issuer
  "sub": "10769150350006150700",           // unique user id
  "aud": "your-client-id",                 // audience
  "email": "alice@example.com",
  "name": "Alice",
  "exp": 1735693200
}

OIDC also defines a standard /userinfo endpoint and discovery documents, making “Sign in with X” interoperable across providers.


How They Fit Together

Technology Layer Answers Produces
JWT Token format — (it’s a container) A signed token
OAuth2 Authorization framework “What can this client access?” Access token
OpenID Connect Authentication layer “Who is this user?” ID token (a JWT)

A real “Sign in with Google” login uses all three: OIDC (built on OAuth2) authenticates the user and returns an ID token and an access token, both of which are typically JWTs.


A Minimal Spring Boot Example

Validating JWTs as a Resource Server

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            // Validate incoming JWT access tokens
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

        return http.build();
    }
}
# application.yml — point at the authorization server's public keys
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com

Spring Security automatically downloads the issuer’s public keys, validates the token’s signature, checks the exp and iss claims, and rejects anything invalid — no manual parsing required.

Logging In as an OAuth2/OIDC Client

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2Login(Customizer.withDefaults()); // handles the full OIDC flow

    return http.build();
}

Best Practices


Summary

Understand these three layers and the “Sign in with Google” button stops being magic and becomes a well-defined, secure handshake you can reason about — and implement.