> For the complete documentation index, see [llms.txt](https://docs.roboflow.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roboflow.com/developer/authentication/sign-in-with-roboflow-developer-reference.md).

# Sign In With Roboflow (Developer Reference)

Integrator reference for Sign in with Roboflow. For registering an app, PKCE, and the authorization flow, see [Sign In With Roboflow (Getting Started)](/developer/authentication/sign-in-with-roboflow-getting-started.md).

## Example app

[roboflow/siwr\_example\_app](https://github.com/roboflow/siwr_example_app) is a minimal Node.js app that implements production OAuth against `app.roboflow.com` and `api.roboflow.com`. Tokens stay in an Express session; the browser never sees the client secret.

```bash
git clone https://github.com/roboflow/siwr_example_app.git
cd siwr_example_app
cp .env.example .env
# Set RF_CLIENT_ID, RF_CLIENT_SECRET, SESSION_SECRET
npm install && npm run dev
```

Register an OAuth app with redirect URI `http://localhost:3001/oauth/callback` and allowed scopes matching the app (`openid`, `profile`, `email`, `workspace:read`, `project:read`, `model:infer`).

| Route                 | Purpose                        |
| --------------------- | ------------------------------ |
| `GET /`               | Landing or signed-in dashboard |
| `GET /oauth/start`    | Start OAuth (PKCE redirect)    |
| `GET /oauth/callback` | Exchange code, show dashboard  |
| `POST /logout`        | Revoke token and clear session |

### PKCE and authorize URL

From [`src/oauth.ts`](https://github.com/roboflow/siwr_example_app/blob/main/src/oauth.ts):

```typescript
export function pkcePair(): { verifier: string; challenge: string } {
    const verifier = crypto.randomBytes(48).toString("base64url");
    const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
    return { verifier, challenge };
}

export function buildAuthorizeUrl(state: string, codeChallenge: string): string {
    const url = new URL(`${config.appHost}/oauth/authorize`);
    url.searchParams.set("response_type", "code");
    url.searchParams.set("client_id", config.clientId);
    url.searchParams.set("redirect_uri", config.redirectUri);
    url.searchParams.set("scope", REQUESTED_SCOPE_STRING);
    url.searchParams.set("state", state);
    url.searchParams.set("code_challenge", codeChallenge);
    url.searchParams.set("code_challenge_method", "S256");
    return url.toString();
}
```

`GET /oauth/start` stores `verifier` and `state` in the session, then redirects to this URL.

### Token exchange (server-side)

```typescript
const body = new URLSearchParams({
    grant_type: "authorization_code",
    code,
    redirect_uri: config.redirectUri,
    client_id: config.clientId,
    client_secret: config.clientSecret,
    code_verifier: codeVerifier
});

const response = await fetch(`${config.appHost}/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body
});
```

### Callback: validate `state`

From [`src/server.ts`](https://github.com/roboflow/siwr_example_app/blob/main/src/server.ts):

```typescript
const pending = req.session.oauth;
req.session.oauth = undefined;

if (!pending || !code || state !== pending.state) {
    // reject: invalid state or missing code
    return;
}

const data = await exchangeCodeForTokens(code, pending.verifier);
req.session.tokens = tokensFromTokenResponse(data);
```

Always clear one-time OAuth session data before exchanging the code.

## Token Endpoint Authentication

The token endpoint supports two methods for confidential clients (public clients omit the secret and rely on PKCE):

| Method                | How it works                                                                                          |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `client_secret_post`  | Send `client_secret` as a form parameter in the request body (default)                                |
| `client_secret_basic` | Send credentials in the HTTP `Authorization: Basic` header (base64-encoded `client_id:client_secret`) |

Choose the method that matches your client or gateway. Set it when creating or editing the OAuth app in **Workspace Settings > Developer**.

Example token exchange with `client_secret_basic`:

```bash
curl -X POST https://app.roboflow.com/oauth/token \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "code_verifier=YOUR_CODE_VERIFIER"
```

## Hosts

| Service               | Base URL                   |
| --------------------- | -------------------------- |
| Sign-in, tokens, OIDC | `https://app.roboflow.com` |
| REST API              | `https://api.roboflow.com` |

## Visibility

Controls which workspaces may sign in with your OAuth app.

| Dashboard option | Who can sign in                                                           |
| ---------------- | ------------------------------------------------------------------------- |
| **Internal**     | Only members of the workspace that created the app                        |
| **Unlisted**     | Any workspace if they have your client ID                                 |
| **Public**       | Unlisted plus listing in the Sign in with Roboflow directory when enabled |

There is no separate "External" visibility type. Cross-workspace integrations use **Unlisted**.

Each customer workspace can also set an **External OAuth apps policy** (allow all, allowlist client IDs, or block all). If sign-in fails outside your workspace, ask the customer to allow your client ID in workspace settings.

## Sign-in and tokens (app.roboflow\.com)

| Purpose         | Method | URL                                                         | Notes                                      |
| --------------- | ------ | ----------------------------------------------------------- | ------------------------------------------ |
| Authorize       | GET    | `https://app.roboflow.com/oauth/authorize`                  | PKCE + `state` required                    |
| Token / refresh | POST   | `https://app.roboflow.com/oauth/token`                      | `application/x-www-form-urlencoded`        |
| Revoke          | POST   | `https://app.roboflow.com/oauth/revoke`                     | Body: `token=...`                          |
| Userinfo        | GET    | `https://app.roboflow.com/oauth/userinfo`                   | Bearer; needs `openid`                     |
| Introspect      | POST   | `https://app.roboflow.com/oauth/introspect`                 | Form `token=...`; client id + secret       |
| Validate        | GET    | `https://app.roboflow.com/oauth/validate`                   | Bearer token; no client credentials needed |
| OIDC discovery  | GET    | `https://app.roboflow.com/.well-known/openid-configuration` |                                            |
| JWKS            | GET    | `https://app.roboflow.com/.well-known/jwks.json`            | Verify `id_token` JWTs                     |

## Using tokens on the REST API

Send `Authorization: Bearer {access_token}` on `api.roboflow.com`. See [Authenticate with the REST API](/developer/rest-api/authenticate-with-the-rest-api.md).

## Common errors

| HTTP                 | Meaning                                    | What to do                                    |
| -------------------- | ------------------------------------------ | --------------------------------------------- |
| 400 `invalid_scope`  | Scope not on app's Allowed list            | Add scope in dashboard or remove from request |
| 401 `OAuthException` | Token expired or revoked                   | Refresh once, then re-prompt sign-in          |
| 403 (scope)          | Token missing scope                        | Re-consent with broader scopes                |
| 403 (other)          | User role lacks permission                 | Workspace admin adjusts role                  |
| Redirect mismatch    | `redirect_uri` does not match registration | Fix dashboard entry or your authorize URL     |

## Security

* Use PKCE on every authorize request.
* Validate `state` on every callback.
* Never expose `client_secret` or refresh tokens to the browser.
* Revoke tokens on sign-out.
* Request minimum scopes; see the full list on [Getting Started](/developer/authentication/sign-in-with-roboflow-getting-started.md#available-scopes).
