> For the complete documentation index, see [llms.txt](https://hassona.gitbook.io/hassona-appsec/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hassona.gitbook.io/hassona-appsec/oauth-in-depth/oauth-2.0-under-the-hood-a-security-focused-deep-dive.md).

# OAuth 2.0 Under the Hood: A Security-Focused Deep Dive

## 1. Protocol Overview

OAuth 2.0 is everywhere — it underpins authentication flows across virtually every modern web application, API, and mobile platform. Yet despite its ubiquity, it remains one of the most consistently misconfigured and misunderstood protocols in production environments. Most implementations get the happy path right. The security tradeoffs are where things break down — choosing the wrong grant type for your client type, misunderstanding the revocation implications of JWT tokens, or underestimating the attack surface of implicit flows that should have been deprecated long ago. This article dissects OAuth 2.0 from the ground up: the protocol's role model, every major grant type and the security reasoning behind its evolution, access token architecture, the difference between public and confidential clients and client authentication methods — ending with a practical decision framework for choosing the right configuration for your threat model.

### 1.1 The Four Roles

<figure><img src="/files/OTaKw2Yy1oo91bIKRcFr" alt=""><figcaption></figcaption></figure>

OAuth 2.0 is built around a clear separation of concerns. each party in the protocol has a distinct responsibility, and understanding these boundaries is essential for identifying where trust is established and where it can break down.

1. Resource owner
   * The entity that owns the protected data at the Resource server typically the end user. Resource owner grants/delegates access to his data to client app.
2. Client application
   * The application that wants to access protected data on behalf of Resource owner with scopes and time limited permissions tied to the access token.
3. Authorization server
   * It is the trust anchor of OAuth flow. It authenticates Resource owner, verifies Client identity ,issues time limited access token tied to authorization permissions and manages token issuance, refreshing and revocation.
4. resource server
   * A server hosting the protected resource. It grants access to user data after validating access token.

### 1.2 Key Terminology

* **access token**: A credential token Issued by the authorization server granting the client a scoped access to resource ownder’s data on resource server. Token could be JWT or Opaque .
* **Auth code**: Temporal short-lived, single-use cod code the client app uses to exchange with access token.
* **client id:** is a unique, public identifier assigned to a client by the authorization server once at registration process.
* **client secret** : A confidential password used by the Client to authenticate itself to the authorization server when requesting an access token. Can be used only with confidential clients.
* **Redirect url** : The callback endpoint/URL that authorization server redirects users back after authentication. It has to be pre-registered URIs — Accepting arbitrary URIs is a direct vector to auth code hijacking and open redirection attacks.
* **Auth scope**: A string that define the specific permissions and scope of data a client wants to access (e.g. `read:profile`, `write:data`). resource owner should give consent to this scope.

## 2. Grant Types

The OAuth grant type determines the exact sequence of interactions between the four parties involved in the OAuth framework and determines how client application obtains the access token. Each grant type fits specific client architecture and selecting the wrong one for your use case is a common source of OAuth vulnerabilities.

The client declares its intended grant type in the initial authorization request. The chosen grant type should be selected during client registration at the authorization server and for sure not all authorization servers support all types.

Let’s take grant types one by one

### 2.1 Implicit Flow (Deprecated)

```mermaid
sequenceDiagram
    participant U as User (Resource Owner)
    participant C as Client APP
    participant AS as AuthZ Server
    participant RS as Resource Server

    U->>C: 1. Access APP
    C->>U: 2. Request AuthZ (Redirect to AS)
    U->>AS: 3. Authenticate and Allow AuthZ
    AS->>U: 4. Include access token back in the redirect URL
    U->>C: (Token delivered via URL fragment)
```

The Implicit flow was a simplified OAuth flow previously recommended for native apps and browser based JavaScript apps (SPAs) where the access token was returned immediately without an extra authorization code exchange step. This approach was simple because these types of public clients didn't carry the burden of holding a secret to exchange for the access token.\
The root issue is that the authorization server returns the access token in the authorization response which is a URL redirection this makes it trivially accessible to:&#x20;

* Access token hijacking— If the attacker is able to send the authorization response to an attacker-controlled URI
* Exposed in browser history — and the more time token lives the more users are exposed to attacks
* **Cross-site script inclusion** — if the page is vulnerable to XSS an attacker can directly extract the token from the URL fragment

Moreover, no standardized method for sender-constraining exists to bind access tokens to a specific client. For these particular reasons, implicit flow is not recommended and most authorization servers do not support it nowadays.

<details>

<summary>flow in detail</summary>

#### 1. Authorization request

The implicit flow starts in much the same way as the authorization code flow. The only major difference is that the `response_type` parameter must be set to `token`.

```
GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=token&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com
```

#### 2. User login and consent

The user logs in and decides whether to consent to the requested permissions or not. This process is exactly the same as for the authorization code flow.

#### 3. Access token grant

If the user gives their consent to the requested access, this is where things start to differ. The OAuth service will redirect the user's browser to the `redirect_uri` specified in the authorization request. However, instead of sending a query parameter containing an authorization code, it will send the access token and other token-specific data as a URL fragment.

```
GET /callback#access_token=z0y9x8w7v6u5&token_type=Bearer&expires_in=5000&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: client-app.com
```

As the access token is sent in a URL fragment, it is never sent directly to the client application. Instead, the client application must use a suitable script to extract the fragment and store it.

#### 4. API call

Once the client application has successfully extracted the access token from the URL fragment, it can use it to make API calls to the OAuth service's `/userinfo` endpoint. Unlike in the authorization code flow, this also happens via the browser.

```
GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5
```

#### 5. Resource grant

The resource server should verify that the token is valid and that it belongs to the current client application. If so, it will respond by sending the requested resource i.e. the user's data based on the scope associated with the access token.

```
{
    "username":"carlos",
    "email":"carlos@carlos-montoya.net"
}
```

The client application can finally use this data for its intended purpose. In the case of OAuth authentication, it will typically be used as an ID to grant the user an authenticated session, effectively logging them in.

</details>

### 2.2 Authorization Code Flow

The Authorization code flow addresses the fundamental weakness of  the implicit flow by introducing an extra exchange step between the client and the authorization server.In this flow the access token is kept entirely out of user's browser.&#x20;

Instead of returning an access token directly in the redirect URI, the authorization server issues a single-use authorization code. The client then exchanges this code for an access token via a direct **server-to-server POST request** — a channel invisible to the browser, proxy logs, and the resource owner.

```mermaid
sequenceDiagram
    participant U as User
    participant C as Client APP (Backend)
    participant AS as AuthZ Server
    participant RS as Resource Server

    U->>C: 1. Access APP
    C->>U: 2. Redirect to Auth Server
    U->>AS: 3. Authenticate & Grant Access
    AS->>U: 4. Redirect to callback URL with Auth Code
    U->>C: 5. Deliver Auth Code in URL
    C->>AS: 6. Auth Code + Client Secret
    AS->>C: 7. Return Access Token
    C->>RS: 8. Access Resources (with Token)
    RS-->>C: Data Returned
```

A significant flaw in this type is there’s no binding between the client exchanging an authorization code for tokens and the client that initiated the authorization request. this allows an attacker who intercepts the code to successfully exchange it for an access token using their own `client_id` and  <kbd>client\_secret</kbd> .

This is the exact attack vector that PKCE was designed to close.

### 2.3 Authorization Code + PKCE

proof key for code exchange (PKCE) pronounced “pixie”, it is an extension to the authorization code flow that introduces a cryptographic binding between the initial authorization request and the token exchange request ensuring that only the client that initiated the the flow can obtain the token.

It was originally designed for public client which can't hold a `client_secret` —such as in mobile, desktop, or single-page apps (SPA’s) where `client_secret` can be extracted through reverse engineering.

PKCE is now recommended for all client types as a defense against two specific attack vectors such as authorization code interception/injection where an attacker who intercepts the authorization code cannot exchange it with the access token without showing the original code verifier.

PKCE replaced the static with storage concern `client_secret` with one time cryptographic proof `code challenge` + `code verifier`

```mermaid
sequenceDiagram
    participant U as User
    participant C as Client (App)
    participant AS as AuthZ Server
    participant RS as Resource APP

    U->>C: 1. Access APP
    Note over C: Generate Verifier & Challenge
    C->>U: 2. Redirect to Auth server with Code Challenge
    U->>AS: 3. Authenticate & Grant Access
    Note over AS: Store Challenge
    AS->>U: 4. Redirect with Auth Code
    U->>C: 5. Deliver Auth Code
    C->>AS: 6. Auth Code + Code Verifier
    Note over AS: Validate Verifier == Challenge
    AS->>C: 7. Return Access Token + ID Token
    C->>RS: 8. Access User Data
    RS-->>C: Data Returned
```

Rather than relying on static `client_secret` which carries storage risk PKCE replaces it with a one-time cryptographic proof `code_challenge` and `code_verifier`

<details>

<summary>Flow in detail</summary>

#### Step 0: The Proof Generation (Client Side)

Before the HTTP requests begin, the **Client** creates two values:

1. **code\_verifier:** A high-entropy cryptographic random string (e.g., `dBjftJeZ4CVP-mB92K27uhbUyd1p6L7qe7hIF5DY_94`).
2. **code\_challenge:** A transformation of the verifier. Usually, the Client hashes the verifier using SHA-256 and then Base64Url encodes it.
   * *Formula:* `Base64Url(SHA256(code_verifier))`

***

#### 1. Authorization Request

The Client sends the **Challenge** to the **OAuth Server**. The server stores this challenge for later verification.

**HTTP Request:**

```
GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=code&scope=openid%20profile&state=ae13d489&code_challenge=K9M-bdH6A6ezH9A-5K499f5&code_challenge_method=S256 HTTP/1.1
Host: oauth-authorization-server.com
```

* **code\_challenge:** The hashed version of your secret string.
* **code\_challenge\_method:** Tells the server you used SHA-256 (`S256`).

***

#### 2. User Login and Consent

The **User** logs into the **OAuth Server** and grants permission. This part remains identical to the standard flow.

***

#### 3. Authorization Code Grant

The **OAuth Server** redirects the **User** back to the **Client** with a temporary code.

**HTTP Response/Redirect:**

```
GET /callback?code=a1b2c3d4e5f6g7h8&state=ae13d489 HTTP/1.1
Host: client-app.com
```

***

#### 4. Access Token Request (The Verification Step)

This is where PKCE differs significantly. Instead of sending a `client_secret` (which a public client doesn't have), the Client sends the original, unhashed **code\_verifier**.

**HTTP Request:**

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

client_id=12345&redirect_uri=https://client-app.com/callback&grant_type=authorization_code&code=a1b2c3d4e5f6g7h8&code_verifier=dBjftJeZ4CVP-mB92K27uhbUyd1p6L7qe7hIF5DY_94
```

**What the OAuth Server does now:**

1. It takes the `code_verifier` from this request.
2. It hashes it using the method specified in Step 1 (`S256`).
3. It compares this new hash to the `code_challenge` it stored earlier.
4. **If they match**, it proves the same Client that started the flow is the one finishing it.

***

#### 5. Access Token Grant

If the hashes match, the **OAuth Server** issues the token.

**HTTP Response:**

```
{
    "access_token": "z0y9x8w7v6u5",
    "token_type": "Bearer",
    "expires_in": 3600
}
```

***

#### 6 & 7. API Call and Resource Grant

The **Client** uses the `access_token` to fetch data from the **Resource Server**, exactly as in the standard flow.

</details>

#### Pixie in public VS confidential clients

In OAuth clients are either “public or “confidential” based on their ability to securely store credentials “secret”.

|                     | Public                                                           | confidential                                                      |
| ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------- |
| definition          | apps that can not store a secret securely                        | apps that can securely store a secret                             |
| is pixie mandatory? | Yes                                                              | No, but it is a best practice                                     |
| Example             | <p>• Types of desktop apps<br>• Native mobile apps<br>• SPAs</p> | • normal web apps with server-side backend (e.g., Django, Spring) |

> Notice: The previous three grant types —from Implicit to Authorization Code and finally PKCE— are the same idea that evolved overtime due to security concerns

### 2.4 Resource Owner Password Credentials (Deprecated)

This grant type requires the client to collect user’s credentials `username` and `password` to exchange with the authorization server for for an access token.

This violates the core security principle of OAuth as it was designed for users to grant a scoped authorization to client without exposing their credentials.

Even if the client is trusted a compromise of the client application results in direct exposure of user credentials, not just scoped time-limited access tokens like in other grant types.

For these reasons ROPC was deprecated and should never be used in modern implementations.

### 2.5 Client Credentials

This grant type has no user involved so it is known as M2M “Machine to Machine” communication used in scenarios where no resource owner is involved and the client acts entirely on its own behalf.

Instead of obtaining user consent, the client authenticates directly to the authorization server using its own credentials and receives an access token scoped to its own permissions — not a user's.

**Common use cases:**

* Backend service-to-service communication
* Automated scripts and cron jobs
* Fetching  general, read-only data without user-specific CRUD operations.

```mermaid
sequenceDiagram
    participant C as Client APP (Service/Backend)
    participant AS as AuthZ Server
    participant RS as Resource Server

    Note over C: Client acts on its own behalf
    C->>AS: 1. Client ID + Client Secret
    AS-->>C: 2. Return Access Token
    C->>RS: 3. Access Resources (with Token)
    RS-->>C: 4. Data Returned
```

A security concern is that a compromised `client_secret` grants an attacker unrestricted access within the defined scope, with no user-level revocation mechanism, for this reason i suggest using stronger alternatives covered in detail in Section 4.

***

## 3. Access Token Architecture

An access token is a credential a client present to the resource server in each request accessing user's protected data it also should specify the level of authorization user has delegated to the client. That's why access token has significant security implications especially in authenticity validation, revocation, object/function level authorization checks, token format and the trust model between the resource server and the authorization server.

OAuth 2.0 does not mandate a specific token format. In practice, two types dominate production implementations: **Opaque tokens** and **JWT tokens** each with fundamentally different security tradeoffs.

### 3.1 Opaque Tokens

Opaque token is “Proof-of-Possession (PoP) / Bound Token / reference” which means only the intended client can use it.

It is a random, unique string (e.g. a UUID,) that has no meaning to the client.

<figure><img src="/files/Nxq2kxOlqgy1araa1OHU" alt=""><figcaption></figcaption></figure>

when using this type of tokens resource server has access to 2 endpoints at the authorization server: 1- `/introspection` ⇒ to validate and get the stored token metadata

2- `/revoke` ⇒ to revoke a token or delete it used to log a user out immediately

### 3.2 JWT Tokens

<figure><img src="/files/5Gba89W4uBwYDIvHKYvy" alt=""><figcaption></figcaption></figure>

JWT is a type of “Bearer / self-contained Tokens” Whoever holds the token can use it this is because it is self contained and doesn’t require a DB to store information about the token like expiration, auth scope etc… .

JWT is a three part structured token containing claims (user ID, expiration, scopes) in base64 format signed by the issuer can be validated locally by the resource server using issuer’s public key (JWKS).

|                                                                                                                 | Opaque                                                                                                                             | JWT                                                                                    |
| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| structure                                                                                                       | A random, unique string                                                                                                            | Three part structured base 64 token                                                    |
| Visibility                                                                                                      | hidden “Issuer only can read related information”                                                                                  | Decoded by anyone                                                                      |
| Size                                                                                                            | small and fixed                                                                                                                    | can be large based on how much information contained                                   |
| requires database?                                                                                              | Yes to store meta data about the token “scope, claims, expiration data, user etc…”                                                 | No it contains all information in header and payload parts                             |
| Revocation                                                                                                      | `instant`                                                                                                                          |                                                                                        |
| resource server can send /revoke request to the issuer to delete instantly for the DB even if it hasn’t expired | Until Expiry                                                                                                                       |                                                                                        |
| since there is no centralized DB containing the token it will be valid until expired                            |                                                                                                                                    |                                                                                        |
| Validation                                                                                                      | only issuer can validate it “Auth server” so resource server has to send and /introspection request to auth server to validate it. | Anyone with the issuer public key can validate it and make sure it hasn’t bee tampered |
| performance                                                                                                     | low \[resource server has to send an introspection to auth server on each API call to validate the token]                          | high \[resource server can validate it locally]                                        |

### 3.3 Validation Flow Comparison

#### JWT flow

```mermaid
sequenceDiagram
    participant a as Client
    participant b as Auth Server
    participant c as Resource API

    a->>b: 1. Request token (what ever grant type)
    Note over b: Sign JWT payload
    b ->> a: 2. Return JWT

    a ->> c: 3. Request + JWT
    c->>c: Verify signature + decode claims
    Note right of c: ✓ no introspection needed

    c-->>a: 4. Protected resource

    Note over a,c: ⚠ Revocation problem — token valid until expiry
```

#### Opaque token flow

```mermaid
sequenceDiagram
    participant a as Client
    participant b as Auth Server
    participant c as Resource API

    a->>b: 1. Request token
    b->>b: Store token data and Generate reference
    b-->>a: 2. Opaque token

    a->>c: 3. Request + token
    c->>b: 4. /introspect (token)
    b->>b: Lookup token and Return metadata
    b-->>c: 5. Token metadata

    c-->>a: 6. Protected resource

    Note over a,c: ✓ Instant revocation — delete from store = immediately invalid
```

***

## 4. OAuth authentication

When a client application interacts with an authorization server, two fundamentally different authentication challenges arise: 1- **authenticating the client application itself**, and 2- **authenticating the end user**.

Authorization server issues access tokens to clients after successfully verifying two things:

1. That the **client application** is who it claims to be (client authentication).
2. That the **resource owner (user)** has granted consent, which often requires verifying who the user is (user authentication).

### 4.1 user authentication

* Most of the time happens using some username+password credentials
* MFA or biometric credentials can be used also
* Happens at the `/authorize` endpoint (loads in user’s browser)

### 4.2 client authentication

it is the process by which the **authorization server verifies the identity of the client application** when requesting the access token.

before choosing the Authentication Method you have to define your client app type first is it a “public app” can store a secret or “confidential app”can’t store a secret

<figure><img src="/files/UyIfXZCnuFnnwIlIWZoY" alt=""><figcaption></figcaption></figure>

**Now let's discuss deeply Client Authentication Methods.**

#### **4.2.1 Client secret (HTTP Basic or POST)**

The most straightforward method. The authorization server issues a `client_id` and a `client_secret` during registration. The client presents (exchanges) these at the /token endpoint .

it has two methods:

1. **HTTP Basic Authentication**

   client includes creds in authorization header of post request in the following format `client_id:client_secret` then encoded as base64

   ```jsx
   POST /oauth2/token HTTP/1.1
   Host:            auth.example.com
   Authorization:   Basic bXlhcHAtY2xpZW50LWlkOnMzY3IzdC12NGx1ZQ==
   Content-Type:    application/x-www-form-urlencoded

   grant_type=client_credentials&scope=read:reports%20write:data
   ```
2. **Post**

   client includes the client\_id and client\_secret in the post request body

```jsx
POST /oauth2/token HTTP/1.1
Host:            auth.example.com
Content-Type:    application/x-www-form-urlencoded

grant_type=client_credentials&scope=read:reports%20write:data&client_id=myapp-client-id&client_secret=s3cr3t-v4lue
```

#### **4.2.2** Mutal TLS (mTLS)

The client presents a TLS client certificate during the TLS handshake. The authorization server verifies it against a pre-registered certificate or a trusted CA.

> client has to provide the auth server with his CA one time at registration in order to be trusted by the auth server.

There are two variants:

* **PKI mutual TLS**: The certificate is signed by a CA trusted by the authorization server.
* **Self-signed certificate binding**: The client's certificate thumbprint is registered directly.

the screenshots below shows registration examples:

<div><figure><img src="/files/wTdRjvHgm5xNtPhdkKNW" alt=""><figcaption></figcaption></figure> <figure><img src="/files/CUFENeCErzbmviZVnjQR" alt=""><figcaption></figcaption></figure></div>

> This method can be used when you don’t want to share hardcoded secret with the client

#### **4.2.3** private key JTW

The client signs a JSON Web Token (JWT) using its private key. The authorization server verifies the signature **using the client's registered public key**.

the screenshot below shows registration example

<figure><img src="/files/C6vEQ5cyvEWaIiOs7fpv" alt=""><figcaption></figcaption></figure>

#### **4.2.4** None

the authorization server may accept the `client_id` alone, without further proof of identity (weekest).

#### **4.2.5** PKCE

as we discussed Pixie before the client replaces the client secret exchange with one time `code_challenge` and `code_verifier`

***

## 5. Decision Framework

To decide which OAuth flow and set up to use you have to go through these four decision points :

1. **User involved?** — Authorization Code family vs. Client Credentials (M2M).
2. **Client type** — confidential backend, SPA, or native app — which determines whether to use `client_secret` or other option like (mTLS or PKCE).
3. **Token type** — JWT (fast local validation, no revocation) vs. Opaque (slow validation, instant revocation via `/revoke`).
4. **Client authentication method** — secret, `private_key_jwt`, mTLS, or PKCE.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hassona.gitbook.io/hassona-appsec/oauth-in-depth/oauth-2.0-under-the-hood-a-security-focused-deep-dive.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
