> 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).


---

# 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://docs.roboflow.com/developer/authentication/sign-in-with-roboflow-developer-reference.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.
