Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion skills/openrouter-oauth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ For other install methods (Claude Code plugin marketplace, Cursor Rules, etc.) s

## Compatibility

Browser environment — requires Web Crypto API, `localStorage`, and `sessionStorage`.
Browser, CLI, and Node.js environments. Browser flows require Web Crypto API, `localStorage`, and `sessionStorage`. Headless flows require Web Crypto API and can keep the verifier in process memory.

## What it covers

Expand All @@ -27,3 +27,4 @@ See [SKILL.md](SKILL.md) for the full reference, including:
- Copy-pasteable auth module with no dependencies
- A ready-to-use "Sign In with OpenRouter" button component
- Programmatic API-key acquisition flows (no UI required)
- Headless authorization for SSH sessions, remote development boxes, and containers without a reachable callback
58 changes: 54 additions & 4 deletions skills/openrouter-oauth/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: openrouter-oauth
description: Implement "Sign In with OpenRouter" using OAuth PKCE — framework-agnostic, no SDK or client registration required. Use when the user wants to add OpenRouter login, authentication, sign-in buttons, OAuth, or AI model inference API keys for browser-based apps. No client registration, no backend, no secrets required.
version: 2.0.0
compatibility: browser (requires Web Crypto API, localStorage, sessionStorage)
description: Implement "Sign In with OpenRouter" using OAuth PKCE — framework-agnostic, no SDK or client registration required. Use when the user wants to add OpenRouter login, authentication, sign-in buttons, OAuth, or AI model inference API keys for browser apps, CLI tools, or remote environments. No client registration, no backend, no secrets required.
version: 2.1.0
compatibility: browser, CLI, or Node.js (requires Web Crypto API; browser flows also use localStorage and sessionStorage)
---

# Sign In with OpenRouter
Expand All @@ -16,7 +16,7 @@ Live demo: [openrouterteam.github.io/sign-in-with-openrouter](https://openrouter
| User wants to… | Do this |
|---|---|
| Add sign-in / login to a web app | Follow the full PKCE flow + button guidance below |
| Get an API key programmatically (no UI) | Just implement the PKCE flow — skip the button section |
| Get an API key programmatically (no UI) | Use the headless / no-callback flow below |
| Use the OpenRouter SDK after auth | Do PKCE here for the key, then see `openrouter-typescript-sdk` skill for `callModel`/streaming |

---
Expand Down Expand Up @@ -48,6 +48,56 @@ https://openrouter.ai/auth?callback_url={url}&code_challenge={challenge}&code_ch
| `code_challenge` | The S256 challenge from Step 1 |
| `code_challenge_method` | Always `S256` |

### Headless / no-callback flow

For SSH sessions, remote development boxes, containers, and other environments where a localhost callback cannot be reached, omit `callback_url` entirely. After the user authorizes, OpenRouter displays the authorization code on screen for them to paste into the app.

```
https://openrouter.ai/auth?code_challenge={challenge}&code_challenge_method=S256&key_label={label}
```

| Param | Value |
|---|---|
| `code_challenge` | The S256 challenge from Step 1 (required) |
| `code_challenge_method` | `S256` (required; `plain` is rejected) |
| `key_label` | Optional app title on the approval screen; defaults to `A command-line app` |

- Do not include `callback_url` or `oauth_client_id`. A no-callback request with `oauth_client_id`, a missing `code_challenge`, or a `plain` challenge method is rejected and redirected to the OpenRouter home page instead of showing a code.
- Keep the `code_verifier` in the process until the user pastes the displayed code. `sessionStorage` and `localStorage` from the browser auth module do not apply in a CLI or Node.js context.
Comment thread
perry-the-pr-maintainer[bot] marked this conversation as resolved.
- The displayed code is single-use and expires 10 minutes after issuance.
- After the user pastes the code, continue with Step 4. The `POST /api/v1/auth/keys` exchange is unchanged.

Minimal Node.js example:

```js
import { createHash, randomBytes } from "node:crypto";
import { createInterface } from "node:readline/promises";

const base64url = (bytes) => Buffer.from(bytes).toString("base64url");
const code_verifier = base64url(randomBytes(32));
const code_challenge = base64url(createHash("sha256").update(code_verifier).digest());
const authorizeUrl = new URL("https://openrouter.ai/auth");
authorizeUrl.searchParams.set("code_challenge", code_challenge);
authorizeUrl.searchParams.set("code_challenge_method", "S256");
authorizeUrl.searchParams.set("key_label", "My command-line app");
console.log(`Open this URL, authorize, then paste the code below:\n${authorizeUrl}`);

const readline = createInterface({ input: process.stdin, output: process.stdout });
const code = (await readline.question("Authorization code: ")).trim();
readline.close();
const response = await fetch("https://openrouter.ai/api/v1/auth/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, code_verifier, code_challenge_method: "S256" }),
});
if (!response.ok) {
console.error(`Key exchange failed (${response.status}): ${await response.text()}`);
process.exitCode = 1;
} else {
console.log(await response.json());
}
```

### Step 3: Handle the redirect back

User returns to your `callback_url` with `?code=` appended. Extract the `code` query parameter.
Expand Down