Grant Type Decision Matrix: "What Type of App Are You Building?"

Choosing the correct OAuth 2.0 flow depends on your application architecture, client trust tier, and device capabilities:

Application Type Client Type Recommended Flow Client Secret? Token Recipients
Server-Side Web App
(Node.js, Express, Next.js SSR, Spring, ASP.NET)
Confidential Authorization Code Flow ✅ Yes (Kept on backend server) ID Token → Web Server
Access Token → Web Server
Single Page App (SPA)
(React, Vue, Angular, Svelte)
Public Auth Code + PKCE (RFC 7636) ❌ No (Never store secret in browser) ID Token → SPA Frontend
Access Token → SPA / API
Native Mobile App
(iOS Swift, Android Kotlin, React Native)
Public Auth Code + PKCE (RFC 7636) ❌ No (Binary can be decompiled) ID Token → Mobile App
Access Token → Mobile App
Daemon / Cron / Microservice
(Background worker, CLI tool, M2M)
Confidential Client Credentials Flow ✅ Yes (Stored in secret manager) Access Token → Daemon Service
(No user / No ID token)
IoT / Smart TV / CLI Device
(Apple TV, PlayStation, headless terminal)
Public Device Authorization Flow (RFC 8628) ❌ No Tokens → Device after secondary phone/browser approval

Authorization Code Flow (Golden Standard - 3-Legged)

The 3-legged authorization code grant is the industry standard for interactive user login and delegated API authorization:

sequenceDiagram
    autonumber
    actor User as 👤 End User
    participant Client as 💻 Public Client (SPA / App)
    participant AuthServer as 🛡️ Authorization Server
    participant API as ⚡ Protected API / Gateway

    Note over Client: Generate code_verifier & code_challenge (S256)
    Client->>AuthServer: 1. GET /authorize?response_type=code&code_challenge=...
    AuthServer->>User: 2. Prompts Login & Consent Screen
    User->>AuthServer: 3. Submits Credentials & Consents
    AuthServer-->>Client: 4. 302 Redirect with ?code=SplxlOBeZQQYbYS6WxSbIA&state=...

    Note over Client,AuthServer: Back Channel Exchange
    Client->>AuthServer: 5. POST /token (grant_type=authorization_code, code, code_verifier)
    Note over AuthServer: Verify SHA256(code_verifier) == code_challenge
    AuthServer-->>Client: 6. 200 OK { access_token, id_token, refresh_token }

    Client->>API: 7. GET /api/resource (Authorization: Bearer <access_token>)
    API-->>Client: 8. 200 OK (Protected JSON Data)
          

Full HTTP Request & Response Examples:

// STEP 1: Front-Channel Authorization Request (Browser GET)
GET /authorize?
  response_type=code
  &client_id=s6BhdRkqt3
  &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
  &scope=openid%20profile%20email
  &state=af0ifjsldkj HTTP/1.1
Host: authorization-server.example.com

// STEP 2: Front-Channel Redirect Response (302 Found)
HTTP/1.1 302 Found
Location: https://client.example.com/cb?
  code=SplxlOBeZQQYbYS6WxSbIA
  &state=af0ifjsldkj

// STEP 3: Back-Channel Token Request (Server-to-Server HTTPS POST)
POST /token HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
&client_id=s6BhdRkqt3
&client_secret=gX1fBat3bV

// STEP 4: Token Response JSON Payload
{
  "access_token": "SlAV32hkKG",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8",
  "scope": "openid profile email"
}

Annotated Parameter Breakdown

1. GET /authorize Request Parameters

ParameterTypePurpose & Security Function
response_type=codeRequiredTells the Authorization Server the client is initiating the Authorization Code Grant.
client_idRequiredThe public identifier assigned to the application during developer registration.
redirect_uriRequiredDestination URL where the user browser returns with the authorization code. Must match registered whitelist.
scopeOptional/RecommendedSpace-separated string specifying requested access boundaries (e.g. openid profile email).
stateRequired for SecurityCryptographic random value verified on callback to prevent Cross-Site Request Forgery (CSRF).

2. POST /token Request Parameters

ParameterTypePurpose & Security Function
grant_type=authorization_codeRequiredInforms the token endpoint that an authorization code is being redeemed for access tokens.
codeRequiredThe single-use, short-lived (30-60s) authorization code received from the front-channel redirect.
redirect_uriRequiredMust match the exact redirect_uri sent in Step 1 to prevent redirect hijacking.
client_id / client_secretConfidential OnlyConfidential credentials verifying the client identity on the back-channel.

PKCE Cryptographic Extension (RFC 7636)

Proof Key for Code Exchange (PKCE) replaces static client secrets for public clients (SPAs and mobile apps) to prevent authorization code interception:

🔐 The PKCE Cryptographic Math
  1. Client Generates Verifier: code_verifier = Cryptographically random string (43–128 characters using [A-Z], [a-z], [0-9], -, ., _, ~).
  2. Client Computes Challenge: code_challenge = BASE64URL(SHA256(code_verifier)).
  3. Front Channel: Client sends code_challenge and code_challenge_method=S256 in the GET /authorize request.
  4. Back Channel: Client sends original plain code_verifier in the POST /token request.
  5. Server Validation: Authorization Server hashes the incoming verifier and checks: BASE64URL(SHA256(code_verifier)) === code_challenge.

Client Credentials Flow (RFC 6749 Section 4.4)

Used for machine-to-machine (M2M) communication without an interactive human user:

sequenceDiagram
    autonumber
    participant Daemon as 🤖 Microservice / Daemon / CLI
    participant IdP as 🔑 Authorization Server
    participant API as 🗄️ Protected Backend API

    Note over Daemon,IdP: Direct Machine-to-Machine (No User Involved)
    Daemon->>IdP: 1. POST /token (grant_type=client_credentials, client_id, client_secret)
    Note over IdP: Authenticate Client Secret & Validate Scopes
    IdP-->>Daemon: 2. 200 OK { access_token: "eyJ...", token_type: "Bearer", expires_in: 3600 }

    Daemon->>API: 3. GET /api/protected/reports (Authorization: Bearer eyJ...)
    Note over API: Verify JWT Signature (JWKS) & Check Scopes
    API-->>Daemon: 4. 200 OK (Internal Reports JSON Payload)
          
POST /token HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=daemon_service_app
&client_secret=secure_daemon_secret
&scope=read:reports%20execute:jobs

// Token Response
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:reports execute:jobs"
}

SAML 2.0 Bearer Assertion Flow (RFC 7522)

Enables an enterprise client that has already authenticated via legacy SAML 2.0 SSO to exchange its signed XML assertion for an OAuth 2.0 Access Token without forcing the user to re-authenticate:

POST /token HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2-bearer
&assertion=PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wi...

Device Authorization Flow (RFC 8628)

Designed for input-constrained devices (smart TVs, gaming consoles, CLI tools) with no browser or keyboard:

📺 Smart TV Screen Display (What the User Sees)

To sign in, visit this URL on your mobile phone or computer:

https://authmatrix.local/activate
CODE: WDJB-MJHT

The smart TV continuously polls the Authorization Server in the background until the user approves the prompt on their phone.

// 1. Device initiates code request
POST /device/code HTTP/1.1
client_id=smart_tv_app

// 2. Authorization Server returns user_code and device_code
{
  "device_code": "GmRhmhcxhwAzkoEqiMEg...",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://authmatrix.local/activate",
  "expires_in": 900,
  "interval": 5
}

// 3. Device polls /token endpoint every 5 seconds
POST /token HTTP/1.1
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=GmRhmhcxhwAzkoEqiMEg...
&client_id=smart_tv_app

Interaction Code Flow (Native Embedded SDKs)

An modern OAuth extension (pioneered by Okta Identity Engine) enabling mobile and desktop client applications to natively orchestrate multi-factor authentication, biometric challenges, and passwordless authentication step-by-step without requiring a full web browser redirect.

Deprecated Flows in OAuth 2.1

⛔ Prohibited in Modern OAuth 2.1 Architectures:
  • Implicit Flow (response_type=token): Returns Access Tokens directly in the browser URL fragment. Highly vulnerable to token extraction from browser history, HTTP referer headers, and malicious browser extensions. Fix: Replace with Auth Code + PKCE.
  • Resource Owner Password Flow (grant_type=password): The application collects the user's master password directly. Re-introduces the password anti-pattern and prevents MFA/FIDO2 passkey adoption. Fix: Migrate to Auth Code or Interaction Code Flow.

Refresh Tokens & Rotation Lineage (RFC 6749 Section 6)

Refresh tokens allow applications to obtain fresh Access Tokens without repeatedly prompting the user to log in:

POST /token HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3

// Token Refresh Response (Issues new token pair)
{
  "access_token": "SlAV32hkKG_NEW",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA_ROTATED"
}
🔄 Refresh Token Rotation & Replay Detection:

Every time a refresh token is exchanged, the Authorization Server invalidates it and issues a fresh refresh token pair. If an attacker intercepts and attempts to use a previously invalidated refresh token, the Authorization Server detects token reuse and immediately invalidates the entire session lineage, locking out the attacker.