Grant Types
Grant Types: Matching the Flow to the Client
OAuth 2.0 defines multiple “grant types” — each is a different protocol flow optimized for a specific type of client. Using the wrong grant type creates security vulnerabilities.
Authorization Code Grant (User-Facing Web/Mobile Apps)
This is the most common and most secure flow for applications where a user is present. The key security property: the access token is never exposed to the user’s browser.
The Problem It Solves
A web application needs to act on behalf of a user (e.g., read their Google Calendar). The app must prove to Google that the user consented, but the app’s backend — not the browser — should hold the sensitive token.
The Flow
sequenceDiagram
participant U as User (Browser)
participant App as Your App (Backend)
participant AS as Auth Server (Google)
participant API as Resource Server (Google Calendar)
U->>App: Click "Sign in with Google"
App->>U: Redirect to Google's auth page
U->>AS: GET /authorize?
response_type=code&
client_id=abc&
redirect_uri=https://app.com/callback&
scope=calendar.read&
state=xyz123
Note over U,AS: User sees Google's consent screen:
"App wants to read your calendar"
U->>AS: User clicks "Allow"
AS->>U: Redirect to https://app.com/callback?code=AUTH_CODE&state=xyz123
U->>App: GET /callback?code=AUTH_CODE&state=xyz123
Note over App: Verify state matches to prevent CSRF
App->>AS: POST /token
grant_type=authorization_code&
code=AUTH_CODE&
client_id=abc&
client_secret=SECRET&
redirect_uri=https://app.com/callback
AS-->>App: {access_token, refresh_token, expires_in: 900}
Note over App: Token stored server-side — never reaches the browser
App->>API: GET /calendar/events
Authorization: Bearer access_token
API-->>App: Calendar events
App-->>U: Rendered calendar pageWhy the extra “code” step? The authorization code is a short-lived, one-time-use intermediary. It’s exchanged for the real token in a back-channel request (server-to-server) that includes the client_secret. This means:
- The access token is never in the browser’s URL bar or history
- The
client_secretproves the app’s identity (a stolen auth code is useless without it) - The exchange happens over a server-to-server HTTPS connection, not through the user’s browser
PKCE: Protecting Public Clients
The Problem It Solves
Mobile apps and single-page applications (SPAs) cannot securely store a client_secret. The app’s code is on the user’s device — any secret embedded in it can be extracted. Without a secret, a stolen authorization code can be exchanged by an attacker.
Attack without PKCE:
1. User clicks "Login" in a mobile app
2. OS opens browser → Google auth page → user approves
3. Google redirects back to the app: myapp://callback?code=AUTH_CODE
4. A malicious app registered for the same URL scheme intercepts the redirect
5. Malicious app exchanges AUTH_CODE for an access token
6. Malicious app now has access to the user's dataHow PKCE Prevents This
PKCE (Proof Key for Code Exchange, pronounced “pixy”) ties the authorization code to the specific client that requested it, without needing a stored secret.
sequenceDiagram
participant App as Mobile App
participant AS as Auth Server
Note over App: Generate random code_verifier (43-128 chars)
code_challenge = SHA256(code_verifier)
App->>AS: GET /authorize?
code_challenge=HASH&
code_challenge_method=S256&
...other params
Note over AS: Store code_challenge with this auth session
AS-->>App: callback?code=AUTH_CODE
App->>AS: POST /token
code=AUTH_CODE&
code_verifier=ORIGINAL_RANDOM_STRING
Note over AS: Compute SHA256(code_verifier)
Compare with stored code_challenge
Match → issue token
AS-->>App: {access_token, refresh_token}Why the attacker can’t use a stolen code: The malicious app intercepts the AUTH_CODE but doesn’t have the code_verifier (it was generated in memory by the legitimate app and never transmitted). Without the verifier, the token exchange fails.
Client Credentials Grant (Service-to-Service)
The Problem It Solves
A backend microservice needs to call another internal API. There is no user involved — the service itself is the client and needs to authenticate as itself.
sequenceDiagram
participant Billing as Billing Service
participant AS as Auth Server
participant API as User API
Billing->>AS: POST /token
grant_type=client_credentials&
client_id=billing-service&
client_secret=SERVICE_SECRET&
scope=users.read
AS-->>Billing: {access_token, expires_in: 3600}
Billing->>API: GET /users/123/subscription
Authorization: Bearer access_token
API-->>Billing: Subscription dataNo browser, no redirect, no user consent. The service proves its identity with its client_id + client_secret (or mTLS certificate) and receives a token scoped to the permissions assigned to that service.
Device Authorization Grant (TVs, CLIs, IoT)
The Problem It Solves
A smart TV, game console, or CLI tool has no browser and limited input capability. The user can’t type a URL or interact with a web-based consent screen on the device itself.
sequenceDiagram
participant TV as Smart TV
participant AS as Auth Server
participant Phone as User's Phone
TV->>AS: POST /device/code
client_id=tv-app&scope=streaming
AS-->>TV: {device_code: "xyz", user_code: "ABCD-1234",
verification_uri: "https://auth.example.com/device"}
Note over TV: Display on screen:
"Go to https://auth.example.com/device
and enter code: ABCD-1234"
Phone->>AS: User visits URL, enters "ABCD-1234"
Phone->>AS: User approves on their phone
loop Poll every 5 seconds
TV->>AS: POST /token
grant_type=device_code&
device_code=xyz
AS-->>TV: {error: "authorization_pending"}
end
Note over Phone,AS: User approves
TV->>AS: POST /token (poll)
AS-->>TV: {access_token, refresh_token}
Note over TV: Now authenticated — can stream contentThe device polls the auth server until the user completes authorization on a separate device with a full browser and keyboard.
Test Your Understanding
In the Authorization Code flow, why add the extra ‘code’ step at all? Why not have the authorization server return the access token directly in the browser redirect?
To keep the token out of the browser. The redirect URL is visible in the address bar, browser history, and server/referrer logs — returning a token there (the old Implicit flow) exposes it. Instead the server returns a short-lived, single-use code, which the app’s backend exchanges for the token over a server-to-server back channel, including the client_secret (or PKCE verifier). The token itself never travels through the user agent.
That’s also why Implicit is deprecated: Authorization Code + PKCE gives browser-only apps the same no-secret capability without ever putting a token in a URL.
You’re adding login to a smart TV app with no keyboard and no browser. Why is Authorization Code (even with PKCE) a poor fit, and what do you use instead?
The TV can’t render a consent page or let the user type credentials — Authorization Code assumes a capable browser on the same device. The Device Authorization Grant solves this: the TV shows a short code and a URL, the user approves on their phone, and the TV polls the token endpoint until authorization completes.
The takeaway: pick the grant by client capability and trust, not habit — Authorization Code + PKCE for browsers/mobile, Client Credentials for service-to-service, Device flow for input-constrained devices.