# Authorization Code Flow

> Implement OAuth 2.0 Authorization Code with PKCE for your Orshot app

- **URL**: https://orshot.com/docs/developers/authorization-code

---

The **Authorization Code flow with PKCE** is the recommended way to authenticate users. It works for web apps, desktop apps, and IDE extensions.

## Flow Overview

1. Your app generates a PKCE code verifier and challenge
2. You redirect the user to Orshot's authorization URL
3. The user signs in (if needed) and approves access to specific workspaces
4. Orshot redirects back to your app with an authorization code
5. Your app exchanges the code + verifier for access and refresh tokens

## Step 1: Generate PKCE Challenge

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Generate a random `code_verifier` and derive a `code_challenge` from it using SHA-256.```javascript
// Generate a random code verifier
const codeVerifier = crypto.randomBytes(32).toString("base64url");

// Derive the challenge using SHA-256
const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");
``````python
import hashlib
import secrets
import base64

code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
```## Step 2: Redirect to Authorization

Build the authorization URL and redirect the user:```
https://orshot.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.com/callback
  &response_type=code
  &code_challenge=GENERATED_CHALLENGE
  &code_challenge_method=S256
  &scope=workspace:read render:generate
  &state=RANDOM_STATE_VALUE
```| Parameter               | Required    | Description                                               |
| ----------------------- | ----------- | --------------------------------------------------------- |
| `client_id`             | Yes         | Your app's client ID                                      |
| `redirect_uri`          | Yes         | Must match a registered redirect URI                      |
| `response_type`         | Yes         | Always `code`                                             |
| `code_challenge`        | Yes         | The PKCE challenge derived in Step 1                      |
| `code_challenge_method` | Yes         | Always `S256`                                             |
| `scope`                 | Yes         | Space-separated list of [scopes](https://orshot.com/docs/developers/scopes) |
| `state`                 | Recommended | Random string to prevent CSRF attacks                     |

## Step 3: User Approves

Orshot shows a consent screen where the user:

- Signs in to their Orshot account (if not already signed in)
- Sees your app's name, logo, and requested permissions
- Selects which workspaces to grant access to
- Clicks "Approve" to continue

## Step 4: Handle the Redirect

After approval, Orshot redirects the user to your `redirect_uri` with an authorization code:```
https://your-app.com/callback?code=osc_abc123...&state=YOUR_STATE_VALUE
```**Always verify the `state` parameter matches what you sent in Step 2** to prevent CSRF attacks.

If the user denies access, the redirect will include an `error` parameter:```
https://your-app.com/callback?error=access_denied&state=YOUR_STATE_VALUE
```## Step 5: Exchange Code for Tokens

POST to the token endpoint to exchange the authorization code for tokens:```bash
curl -X POST https://api.orshot.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "osc_abc123...",
    "redirect_uri": "https://your-app.com/callback",
    "code_verifier": "YOUR_CODE_VERIFIER"
  }'
```**Response:**```json
{
  "access_token": "ost_...",
  "refresh_token": "osr_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "workspace:read render:generate",
  "user_id": "user-uuid",
  "workspace_ids": ["workspace-uuid-1", "workspace-uuid-2"]
}
```| Field           | Description                                                                                   |
| --------------- | --------------------------------------------------------------------------------------------- |
| `access_token`  | Use this in API requests (`Authorization: Bearer ost_...`)                                    |
| `refresh_token` | Use this to get new access tokens (see [Token Management](https://orshot.com/docs/developers/token-management)) |
| `expires_in`    | Access token lifetime in seconds (900 = 15 minutes)                                           |
| `workspace_ids` | The workspaces the user granted access to                                                     |

## Full Example (Node.js)```javascript
import crypto from "crypto";
import express from "express";

const app = express();
const CLIENT_ID = process.env.ORSHOT_CLIENT_ID;
const CLIENT_SECRET = process.env.ORSHOT_CLIENT_SECRET;
const REDIRECT_URI = "http://localhost:3000/callback";

// Store verifiers by state (use a proper session store in production)
const pendingAuth = new Map();

app.get("/login", (req, res) => {
  const state = crypto.randomBytes(16).toString("hex");
  const codeVerifier = crypto.randomBytes(32).toString("base64url");
  const codeChallenge = crypto
    .createHash("sha256")
    .update(codeVerifier)
    .digest("base64url");

  pendingAuth.set(state, codeVerifier);

  const url = new URL("https://orshot.com/oauth/authorize");
  url.searchParams.set("client_id", CLIENT_ID);
  url.searchParams.set("redirect_uri", REDIRECT_URI);
  url.searchParams.set("response_type", "code");
  url.searchParams.set("code_challenge", codeChallenge);
  url.searchParams.set("code_challenge_method", "S256");
  url.searchParams.set("scope", "workspace:read render:generate");
  url.searchParams.set("state", state);

  res.redirect(url.toString());
});

app.get("/callback", async (req, res) => {
  const { code, state } = req.query;
  const codeVerifier = pendingAuth.get(state);

  if (!codeVerifier) {
    return res.status(400).send("Invalid state");
  }
  pendingAuth.delete(state);

  const response = await fetch("https://api.orshot.com/v1/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "authorization_code",
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      redirect_uri: REDIRECT_URI,
      code_verifier: codeVerifier,
    }),
  });

  const tokens = await response.json();
  // Store tokens securely — tokens.access_token, tokens.refresh_token
  res.json({ success: true, workspace_ids: tokens.workspace_ids });
});

app.listen(3000);
```## Error Responses

| Error                | Description                                                                                                               |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request`    | Missing or invalid parameters                                                                                             |
| `invalid_client`     | Unknown or disabled client ID                                                                                             |
| `invalid_grant`      | Code expired, already used, or PKCE verifier mismatch                                                                     |
| `invalid_scope`      | Requested scope not allowed for this client                                                                               |
| `access_denied`      | User denied the authorization request, or the app is in development mode and the user is not the owner or a test user     |
| `sandbox_restricted` | The app is in [development mode](https://orshot.com/docs/developers/register-app#development-mode) and the user is not authorized to use it |