> ## Documentation Index
> Fetch the complete documentation index at: https://developer.buildpass.global/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.0 Authorization Code Flow

> Secure user authorization with PKCE for integrator applications

BuildPass supports **OAuth 2.0 Authorization Code Grant with PKCE** (Proof Key for Code Exchange) for integrator applications that need to access builder data on behalf of users. This flow is ideal for third-party applications that require user consent.

## Overview

The Authorization Code flow allows your application to:

1. Direct users to BuildPass for authentication and consent
2. Receive an authorization code after user approval
3. Exchange the code for access and refresh tokens
4. Use access tokens to make API requests on behalf of the user
5. Use refresh tokens to obtain new access tokens

## When to Use This Flow

Use the Authorization Code flow when:

* Your application needs to access BuildPass data **on behalf of a specific user/builder**
* You want users to explicitly authorize your application
* You need long-lived access via refresh tokens
* You're building a mobile app, SPA, or desktop application (public clients)
* You're building a backend application that requires user authorization (confidential clients)

<Note>
  For server-to-server integrations without user involvement, use the [Client
  Credentials flow](/features/authentication) instead.
</Note>

<Note>
  External API integrations authorize against `https://api.buildpass.global` and
  receive tokens for the `https://api.buildpass.global` audience. BuildPass AI
  MCP connectors use regional OAuth and MCP resource URLs, which are separate
  from the External API integration audience.
</Note>

## Client Types and Authentication

This flow works with both **Confidential** and **Public** clients:

### Confidential Clients

Backend applications that can securely store a `client_secret`. Must authenticate with both `client_id` and `client_secret` when exchanging codes for tokens.

**Examples:** Web servers, backend services

### Public Clients

Applications that cannot securely store secrets. Authenticate with only `client_id` and rely on PKCE for security.

**Examples:** Mobile apps, single-page applications, desktop apps

<Note>
  For public BuildPass AI MCP clients that request `execute:ai_agent_tools`, loopback callback hosts are treated as aliases during authorization-code exchange. This allows native MCP clients to authorize with `http://127.0.0.1:<port>/callback` and exchange the code with `http://localhost:<same-port>/callback`, or the reverse. The protocol, path, query string, and non-loopback host still need to match.
</Note>

<Warning>
  **PKCE is required for all authorization requests**, regardless of client
  type. This provides an additional layer of security beyond traditional OAuth
  2.0.
</Warning>

## Security Features

BuildPass implements industry-standard security measures:

* ✅ **PKCE (RFC 7636)** - Required for all authorization requests
* ✅ **State Parameter** - Prevents CSRF attacks
* ✅ **Authorization Code Reuse Protection** - Codes are single-use only
* ✅ **Refresh Token Rotation** - New refresh token issued on each use
* ✅ **Token Revocation** - RFC 7009 compliant revocation endpoint
* ✅ **Rate Limiting** - Protects against abuse
* ✅ **Comprehensive Audit Logging** - All authorization events tracked

## Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant App as Your Application
    participant Browser as User Browser
    participant BP as BuildPass
    participant API as BuildPass API

    App->>App: Generate code_verifier & code_challenge
    App->>Browser: Redirect to /oauth/authorize
    Browser->>BP: Authorization request (with PKCE)
    BP->>Browser: Show consent screen
    Browser->>BP: User approves
    BP->>Browser: Redirect with authorization code
    Browser->>App: Authorization code received
    App->>API: POST /oauth/token (code + code_verifier)
    API->>App: access_token + refresh_token
    App->>API: API requests with access_token
```

## Step-by-Step Implementation

### Step 1: Generate PKCE Values

Before redirecting users, generate a code verifier and challenge:

```javascript theme={null}
// Generate random code_verifier (43-128 characters)
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

// Create code_challenge from verifier using SHA256
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hash = await crypto.subtle.digest("SHA-256", data);
  return base64UrlEncode(new Uint8Array(hash));
}

function base64UrlEncode(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
}
```

<Warning>
  Store the `code_verifier` securely in your session - you'll need it in Step 3!
</Warning>

### Step 2: Redirect User to Authorization Endpoint

Redirect the user to BuildPass for authorization:

```
GET https://api.buildpass.global/oauth/authorize
```

**Required Parameters:**

| Parameter               | Description                                                                          |
| ----------------------- | ------------------------------------------------------------------------------------ |
| `client_id`             | Your application's client ID                                                         |
| `redirect_uri`          | Registered callback URL where BuildPass will send the authorization code             |
| `response_type`         | Must be `code`                                                                       |
| `scope`                 | Space-separated list of requested scopes (e.g., `read:builders read:subcontractors`) |
| `code_challenge`        | Base64-URL-encoded SHA256 hash of your code\_verifier                                |
| `code_challenge_method` | Must be `S256` (SHA256)                                                              |
| `state`                 | Random string to prevent CSRF attacks                                                |

**Optional Parameters:**

| Parameter    | Description                                                    |
| ------------ | -------------------------------------------------------------- |
| `builder_id` | Pre-select a specific builder (if user has access to multiple) |

<Note>
  You do **not** need to send `X-BuildPass-Api-Version` for `/oauth/authorize`.
  Browser redirects cannot include custom headers, so this endpoint defaults to
  API version `v1` when omitted.
</Note>

**Example Authorization URL:**

```javascript theme={null}
const authUrl = new URL("https://api.buildpass.global/oauth/authorize");
authUrl.searchParams.set("client_id", "your_client_id");
authUrl.searchParams.set("redirect_uri", "https://yourapp.com/callback");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", "read:builders read:subcontractors");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", randomState);

// Redirect user
window.location.href = authUrl.toString();
```

### Step 3: Handle the Authorization Callback

After user approval, BuildPass redirects back to your `redirect_uri` with:

**Success Response:**

```
https://yourapp.com/callback?code=abc123&state=xyz789
```

**Error Response:**

```
https://yourapp.com/callback?error=access_denied&error_description=User+denied+consent&state=xyz789
```

<Warning>
  Always validate the `state` parameter matches what you sent to prevent CSRF
  attacks!
</Warning>

### Step 4: Exchange Authorization Code for Tokens

Exchange the authorization code for access and refresh tokens:

**For Confidential Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/token \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Basic base64(client_id:client_secret)' \
  --data '{
    "grant_type": "authorization_code",
    "code": "received_authorization_code",
    "redirect_uri": "https://yourapp.com/callback",
    "code_verifier": "your_stored_code_verifier"
  }'
```

**For Public Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/token \
  --header 'Content-Type: application/json' \
  --data '{
    "grant_type": "authorization_code",
    "client_id": "your_client_id",
    "code": "received_authorization_code",
    "redirect_uri": "https://yourapp.com/callback",
    "code_verifier": "your_stored_code_verifier"
  }'
```

**Required Parameters:**

| Parameter       | Description                                           |
| --------------- | ----------------------------------------------------- |
| `grant_type`    | Must be `authorization_code`                          |
| `code`          | Authorization code from callback                      |
| `redirect_uri`  | Must match the original request                       |
| `code_verifier` | Original code verifier used to generate the challenge |
| `client_id`     | Required for public clients (in request body)         |

**Client Authentication:**

**Confidential Clients** - Provide credentials via **HTTP Basic Authentication** (recommended):

```
Authorization: Basic base64encode(client_id:client_secret)
```

Or in the request body (alternative):

```json theme={null}
{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  ...
}
```

**Public Clients** - Include only `client_id` in the request body:

```json theme={null}
{
  "client_id": "your_client_id",
  ...
}
```

<Warning>
  Public clients must **never** include a `client_secret`. Security for public
  clients relies entirely on PKCE and the registered redirect URIs.
</Warning>

**Success Response:**

```json theme={null}
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "550e8400-e29b-41d4-a716-446655440000",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:builders read:subcontractors"
}
```

### Step 5: Fetch the authorised user identity

After exchanging the authorization code, call `GET /me` with the access token to retrieve the BuildPass user identity and the builders they consented for.

The `/me` response now includes signed asset URLs you can use directly in your integration:

* `profilePictureUrl` - signed URL for the authorised user's BuildPass profile photo, if one is configured
* `memberships[].builderLogoUrl` - signed URL for each consented builder's company logo, if one is configured

These URLs are especially useful when you want to mirror a connected user's BuildPass avatar or company branding into your own product.

**Token Details:**

* **access\_token**: Use this to make API requests (lifetime: **1 hour**)
* **refresh\_token**: Use this to get new access tokens (lifetime: **30 days**)
* **expires\_in**: Access token lifetime in seconds (3600 = 1 hour)
* **scope**: Granted scopes (may differ from requested scopes)

### Step 6: Use the Access Token

Include the access token in API requests:

```bash theme={null}
curl --request GET \
  --url https://api.buildpass.global/builders \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```

### Step 7: Refresh the Access Token

When the access token expires, use the refresh token to get a new one:

**For Confidential Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/token \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Basic base64(client_id:client_secret)' \
  --data '{
    "grant_type": "refresh_token",
    "refresh_token": "your_refresh_token"
  }'
```

**For Public Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/token \
  --header 'Content-Type: application/json' \
  --data '{
    "grant_type": "refresh_token",
    "client_id": "your_client_id",
    "refresh_token": "your_refresh_token"
  }'
```

**Response:**

```json theme={null}
{
  "access_token": "new_access_token",
  "refresh_token": "new_refresh_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:builders read:subcontractors"
}
```

<Note>
  **Refresh Token Rotation:** BuildPass automatically rotates refresh tokens.
  The old refresh token is revoked and a new one is issued. Always store the new
  `refresh_token` from the response.
</Note>

### Step 8: Revoke Tokens (Optional)

When a user disconnects your integration or you need to invalidate tokens:

**For Confidential Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/revoke \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Basic base64(client_id:client_secret)' \
  --data '{
    "token": "token_to_revoke",
    "token_type_hint": "refresh_token"
  }'
```

**For Public Clients:**

```bash theme={null}
curl --request POST \
  --url https://api.buildpass.global/oauth/revoke \
  --header 'Content-Type: application/json' \
  --data '{
    "client_id": "your_client_id",
    "token": "token_to_revoke",
    "token_type_hint": "refresh_token"
  }'
```

**Parameters:**

| Parameter         | Description                                   |
| ----------------- | --------------------------------------------- |
| `token`           | The token to revoke (access or refresh token) |
| `token_type_hint` | Optional: `access_token` or `refresh_token`   |

<Note>
  Per RFC 7009, the endpoint returns `200 OK` regardless of whether the token
  was found, preventing token scanning attacks.
</Note>

## Available Scopes

Request only the scopes your application needs:

| Scope                    | Description                       |
| ------------------------ | --------------------------------- |
| `read:builders`          | Access builder information        |
| `read:subcontractors`    | Access subcontractor information  |
| `read:projects`          | Access project information        |
| `read:insurances`        | Access insurance certificates     |
| `read:prequalifications` | Access prequalification documents |
| `read:contacts`          | Access contact information        |
| `read:swms`              | Access SWMS documents             |
| `read:timesheets`        | Access timesheet data             |
| `read:inductions`        | Access induction records          |

## Scope Changes and Re-Authorization

<Warning>
  **Important:** If your application's allowed scopes change, users must
  **re-authorize** your application to grant the new permissions.
</Warning>

When you request different scopes:

1. Users will see a new consent screen showing the updated permissions
2. Previous authorization codes and tokens remain valid with their original scopes
3. New tokens will include the updated scopes

We do not currently support incremental consent (automatically upgrading existing tokens with new scopes).

## Token Security Best Practices

### Storage Guidelines

<Warning>**Critical Security Requirements:**</Warning>

**For All Clients:**

1. **Encrypt tokens at rest** - Use your platform's secure storage
2. **Never log tokens** - Treat them as sensitive credentials
3. **Use HTTPS only** - All communication must be over TLS
4. **Validate redirect URIs** - Ensure they match registered URIs exactly

**For Confidential Clients:**

1. **Protect client\_secret** - Store securely using secret managers (AWS Secrets Manager, HashiCorp Vault, etc.)
2. **Never expose secrets client-side** - Keep credentials server-side only
3. **Use HTTP Basic Auth** - Preferred over body parameters
4. **Rotate credentials periodically** - Contact BuildPass for rotation

**For Public Clients:**

1. **Never store tokens in browser localStorage or sessionStorage** - Vulnerable to XSS attacks
2. **Use platform-specific secure storage:**
   * iOS: Keychain
   * Android: KeyStore
   * Desktop: OS-specific credential managers
3. **Implement proper PKCE** - Generate cryptographically random verifiers
4. **Validate state parameter** - Prevent CSRF attacks
5. **Use short-lived in-memory storage** where possible for access tokens

### Token Handling

```javascript theme={null}
// ✅ GOOD: Secure token storage (Node.js example)
const encryptedToken = encrypt(accessToken, encryptionKey);
await secureDb.store("access_token", encryptedToken);

// ❌ BAD: Never do this
localStorage.setItem("access_token", accessToken); // Vulnerable to XSS!
console.log("Token:", accessToken); // Never log tokens!
```

### Detecting Token Expiration

```javascript theme={null}
// Check expires_in from token response
const expiresAt = Date.now() + response.expires_in * 1000;

// Before making API requests:
if (Date.now() >= expiresAt) {
  // Token expired, refresh it
  await refreshAccessToken();
}
```

## Error Responses

All OAuth errors follow RFC 6749 standards:

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
```

**Common Error Codes:**

| Error Code        | Description                        | HTTP Status |
| ----------------- | ---------------------------------- | ----------- |
| `invalid_request` | Missing or malformed parameters    | 400         |
| `invalid_client`  | Invalid client credentials         | 401         |
| `invalid_grant`   | Invalid/expired authorization code | 400         |
| `invalid_scope`   | Requested scopes not allowed       | 400         |
| `access_denied`   | User denied authorization          | 400         |
| `server_error`    | Internal server error              | 500         |

## Rate Limits

OAuth endpoints are rate-limited to prevent abuse:

| Endpoint           | Limit       | Window     |
| ------------------ | ----------- | ---------- |
| `/oauth/authorize` | 30 requests | 10 seconds |
| `/oauth/token`     | 60 requests | 10 seconds |
| `/oauth/revoke`    | 30 requests | 10 seconds |

Rate limit headers are included in responses:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000
Retry-After: 5
```

## Authorization Code Reuse Protection

<Warning>
  **Security Feature:** Authorization codes can only be used **once**. Attempting to reuse a code will:

  1. Return an `invalid_grant` error
  2. **Revoke all refresh tokens** associated with that authorization
  3. Require the user to re-authorize your application

  This protects against authorization code interception attacks.
</Warning>

## Testing Your Integration

### Step-by-Step Checklist

* [ ] Generate valid PKCE code\_verifier and code\_challenge
* [ ] Construct authorization URL with all required parameters
* [ ] Implement state parameter validation in callback
* [ ] Handle both success and error callbacks
* [ ] Exchange authorization code for tokens within 10 minutes
* [ ] Store refresh token securely
* [ ] Implement token refresh before expiration
* [ ] Handle token revocation
* [ ] Test scope permission boundaries
* [ ] Test authorization code reuse (should fail)
* [ ] Test expired authorization code (should fail)
* [ ] Test mismatched redirect\_uri (should fail)
* [ ] Test invalid code\_verifier (should fail)

### Example Error Scenarios

**Expired Authorization Code:**

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
```

**Invalid PKCE Verifier:**

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Invalid PKCE verifier"
}
```

**Authorization Code Reused:**

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Authorization code has already been used"
}
```

## RFC Compliance

BuildPass OAuth implementation is fully compliant with:

* ✅ **RFC 6749** - OAuth 2.0 Authorization Framework
* ✅ **RFC 7636** - Proof Key for Code Exchange (PKCE)
* ✅ **RFC 7009** - Token Revocation
* ✅ **RFC 6750** - Bearer Token Usage

## Support

For questions or issues with OAuth integration:

* Email: [support@buildpass.com.au](mailto:support@buildpass.com.au)
* Documentation: [https://docs.buildpass.global](https://docs.buildpass.global)

<Note>
  Need help with your integration? Our team is here to assist with
  implementation questions and troubleshooting.
</Note>
