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
| Role | Example |
|---|---|
| Resource Owner | Alice (the end user) |
| Client | Your Spring Boot application |
| Authorization Server | Google, GitHub, Okta, your own Auth Server |
| Resource Server | Google 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:
1. Authorization Code Flow (Recommended for web/mobile)
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)/userinfoendpoint — 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:
| Scope | What it grants |
|---|---|
openid | OIDC: include id_token, enables /userinfo |
profile | Name, picture, locale |
email | Email address |
offline_access | Issue a refresh token |
read:products | Custom 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:
| Endpoint | Purpose |
|---|---|
/authorize | Start the authorization flow (redirect user here) |
/token | Exchange code for tokens, or use refresh token |
/userinfo | Get user profile (OIDC) |
/introspect | Validate an opaque token |
/revoke | Revoke an access or refresh token |
/.well-known/openid-configuration | Metadata: discover all endpoint URLs |
/.well-known/jwks.json | Public 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-configurationfor auto-discovery — one URL configures everything.
Next: Article 13 implements OAuth2 login — “Sign in with Google/GitHub” in a Spring Boot app.