OAuth2 Fundamentals: Grant Types and Flows

OAuth2 in One Sentence

OAuth2 lets a user grant a third-party application limited access to their account on another service — without giving the third party their password.

Classic example: “Sign in with Google.” Your app never sees the user’s Google password. Google verifies the user’s identity and gives your app a token with limited permissions.


The Four OAuth2 Roles

flowchart TD
    RO["**Resource Owner**\nThe user who owns the data\n(e.g. Alice, the Google account owner)"]
    Client["**Client**\nThe application requesting access\n(e.g. your Spring Boot app)"]
    AS["**Authorization Server**\nIssues access tokens after user consent\n(e.g. Google's OAuth server)"]
    RS["**Resource Server**\nHosts protected resources, accepts tokens\n(e.g. Google Calendar API)"]

    RO -->|"Grants consent"| AS
    AS -->|"Issues token"| Client
    Client -->|"Presents token"| RS
    RS -->|"Returns protected data"| Client
RoleExample
Resource OwnerAlice (the end user)
ClientYour Spring Boot application
Authorization ServerGoogle, GitHub, Okta, your own Auth Server
Resource ServerGoogle Calendar API, GitHub API, your own API

OAuth2 Grant Types

Grant types are the flows by which a client gets an access token. OAuth2 defines four. RFC 9126 (OAuth2.1) consolidates them:

The user is redirected to the authorization server, logs in, grants consent, and the server redirects back to your app with a code. Your app exchanges the code for tokens server-side.

sequenceDiagram
    participant User as User (Browser)
    participant App as Your App (Client)
    participant AS as Authorization Server

    User->>App: "Sign in with Google"
    App->>User: Redirect to:\nhttps://accounts.google.com/o/oauth2/auth?\nclient_id=...&redirect_uri=...&scope=openid+email&state=xyz
    User->>AS: User logs in + grants consent
    AS->>User: Redirect to:\nhttps://yourapp.com/callback?code=abc123&state=xyz
    User->>App: GET /callback?code=abc123&state=xyz
    App->>App: Verify state matches (CSRF protection)
    App->>AS: POST /token\n{code: abc123, client_secret: ...}
    AS-->>App: {access_token, refresh_token, id_token}
    App->>AS: GET /userinfo (with access_token)
    AS-->>App: {sub, email, name, ...}
    App->>User: Logged in as alice@gmail.com

The code is short-lived (minutes) and single-use. It is exchanged server-to-server (back channel), so the access token is never exposed to the browser.

2. Authorization Code + PKCE (for public clients)

For mobile apps and SPAs that cannot keep a client_secret (it would be in the browser or app bundle, visible to anyone), PKCE (Proof Key for Code Exchange) replaces the secret:

sequenceDiagram
    participant SPA as SPA / Mobile App
    participant AS as Authorization Server

    SPA->>SPA: Generate code_verifier (random)\ncode_challenge = SHA256(code_verifier)
    SPA->>AS: Auth request with code_challenge
    AS-->>SPA: code=abc123
    SPA->>AS: Token request with code + code_verifier (raw)
    AS->>AS: SHA256(code_verifier) == code_challenge?
    AS-->>SPA: access_token (no client_secret needed)

PKCE ensures only the original requester can exchange the code — even if the authorization code is intercepted in the redirect.

3. Client Credentials (Machine-to-Machine)

No user involved. A service authenticates directly to the authorization server using its client_id and client_secret:

sequenceDiagram
    participant ServiceA as Service A (Client)
    participant AS as Authorization Server
    participant ServiceB as Service B (Resource Server)

    ServiceA->>AS: POST /token\n{client_id, client_secret, grant_type=client_credentials}
    AS-->>ServiceA: {access_token, expires_in}
    ServiceA->>ServiceB: GET /api/data\nAuthorization: Bearer {access_token}
    ServiceB-->>ServiceA: 200 OK {data}

Use for service-to-service authentication where there is no end user.

4. Refresh Token Grant

Exchange a refresh token for a new access token (covered in Article 9):

POST /token
{grant_type=refresh_token, refresh_token=rt_xyz, client_id=..., client_secret=...}

OAuth2 vs OpenID Connect (OIDC)

OAuth2 is an authorization framework — it issues access_tokens that grant access to APIs. It does not define how to verify user identity.

OpenID Connect (OIDC) is a thin identity layer on top of OAuth2. It adds:

  • id_token — a JWT containing the user’s identity (sub, email, name)
  • /userinfo endpoint — fetches user attributes
  • Standardized scopes: openid, profile, email, phone, address
block-beta
  columns 1
  A["OpenID Connect (Identity Layer)"]
  B["OAuth2 (Authorization Framework)"]
  C["HTTP / TLS (Transport)"]

When you “Sign in with Google,” you use OIDC: Google’s authorization server returns both an access_token (for the API) and an id_token (proving who the user is).

OIDC Claims in the id_token

{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",         // unique user ID
  "aud": "client_id_of_your_app",
  "exp": 1714003600,
  "iat": 1714000000,
  "email": "alice@gmail.com",
  "email_verified": true,
  "name": "Alice Smith",
  "picture": "https://lh3.googleusercontent.com/..."
}

OAuth2 Scopes

Scopes define what the access token is allowed to do:

ScopeWhat it grants
openidOIDC: include id_token, enables /userinfo
profileName, picture, locale
emailEmail address
offline_accessIssue a refresh token
read:productsCustom scope for your API
admin:*Custom admin scopes

Tokens in OAuth2

block-beta
  columns 3
  AT["**Access Token**\nShort-lived (minutes/hours)\nPresented to resource server\nJWT or opaque"]
  RT["**Refresh Token**\nLong-lived (days/weeks)\nExchanged for new access token\nOpaque, stored securely"]
  IT["**ID Token**\nOIDC only\nJWT with user identity\nConsumed by client, not API"]

Access token: presented to the resource server (your API). Can be a JWT (self-contained) or opaque (must be introspected).

Refresh token: exchanged at the authorization server for a new access token. Never presented to the resource server.

ID token: OIDC only. Consumed by the client (your app) to know who the user is. Never presented to a resource server.


Token Validation

JWT Access Token (Self-Contained)

The resource server validates the JWT signature using the authorization server’s public key:

1. Download public key from {auth-server}/.well-known/jwks.json
2. Verify JWT signature with public key
3. Check exp, iss, aud claims
4. Extract sub, scopes, roles from claims
5. Allow or deny based on scopes/claims

Opaque Access Token (Introspection)

The resource server calls the authorization server’s introspection endpoint:

POST /introspect
{token: opaque_token_value, client_id: ..., client_secret: ...}

Response:
{
  "active": true,
  "sub": "alice",
  "scope": "read:products",
  "exp": 1714003600
}

JWT is preferred for performance (no network call per request); opaque is preferred for instant revocation.


OAuth2 Endpoints

Every authorization server exposes these standard endpoints:

EndpointPurpose
/authorizeStart the authorization flow (redirect user here)
/tokenExchange code for tokens, or use refresh token
/userinfoGet user profile (OIDC)
/introspectValidate an opaque token
/revokeRevoke an access or refresh token
/.well-known/openid-configurationMetadata: discover all endpoint URLs
/.well-known/jwks.jsonPublic keys for JWT validation

The OpenID Connect discovery document (/.well-known/openid-configuration) lets Spring Security auto-configure everything from a single URL:

spring:
  security:
    oauth2:
      client:
        provider:
          google:
            issuer-uri: https://accounts.google.com  # Spring discovers all endpoints from here

Security Considerations

mindmap
  root((OAuth2 Security))
    State Parameter
      Prevent CSRF on redirect
      Random, unpredictable value
      Validate on callback
    PKCE
      Required for public clients
      Prevents code interception
      Use S256 challenge method
    Redirect URI
      Exact match validation
      No wildcards in production
      HTTPS only
    Token Storage
      Access token in memory
      Refresh token in HttpOnly cookie
      Never in localStorage
    Scopes
      Request minimum scopes
      Validate scopes server-side
      Principle of least privilege
    Token Expiry
      Short access token lifetime
      Rotate refresh tokens
      Revoke on logout

Summary

  • OAuth2 defines four roles: Resource Owner (user), Client (your app), Authorization Server (issues tokens), Resource Server (your API).
  • Authorization Code + PKCE is the standard flow for web and mobile apps — user-driven, secure.
  • Client Credentials is for machine-to-machine — no user, service authenticates directly.
  • OpenID Connect adds identity (id_token with user claims) on top of OAuth2’s authorization.
  • Access tokens are short-lived (presented to APIs); refresh tokens are long-lived (exchanged for new access tokens).
  • Use /.well-known/openid-configuration for auto-discovery — one URL configures everything.

Next: Article 13 implements OAuth2 login — “Sign in with Google/GitHub” in a Spring Boot app.