mcpField
O
openrouter-oauth

openrouter-oauth

github.com/openrouterteam開発309302 回インストール1 日前に更新
リポジトリ

openrouter-oauth について

openrouter-oauthは、WebアプリにOpenRouterログインを追加するためのスキルです。クライアント登録やバックエンドを必要としないPKCEフローでAPIキーを取得する方法と、ログインボタンの実装を説明します。

主な機能

  1. 01OAuth PKCEフローの実装
  2. 02認可コードからAPIキーへの交換
  3. 03検証子とキーの保存管理
  4. 04タブ間でのログイン状態の同期
  5. 05複数スタイルのログインボタン作成

活用シーン

  1. 01WebアプリへのOpenRouterログイン追加
  2. 02ログインUIなしでのAPIキー取得
  3. 03認証をOpenRouter API呼び出しに接続

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/openrouterteam/skills --skill openrouter-oauth

インストール後、Claude Code で/openrouter-oauthを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

OpenRouter でサインイン

あらゆる Web アプリに OAuth ログインを追加できます。ユーザーが OpenRouter で認証すると、アプリに API キーが渡されます。クライアント登録もバックエンドもシークレットも必要ありません。どのフレームワークでも動作します。

ライブデモ:openrouterteam.github.io/sign-in-with-openrouter

判断フローチャート

ユーザーの目的実行する内容
Web アプリにサインイン/ログインを追加する以下の完全な PKCE フローとボタンのガイダンスに従います
プログラムから API キーを取得する(UI なし)PKCE フローを実装するだけです。ボタンのセクションは省略できます
認証後に OpenRouter SDK を使用するここでキーを取得するための PKCE を実行し、callModel/ストリーミングについては openrouter-typescript-sdk スキルを参照してください

OAuth PKCE フロー

クライアント ID やシークレットは不要です。PKCE チャレンジだけで本人確認できます。

ステップ 1:ベリファイアとチャレンジを生成する
code_verifier  = base64url(32 random bytes)
code_challenge = base64url(SHA-256(code_verifier))
  • ランダムなバイト列には crypto.getRandomValues(new Uint8Array(32)) を使用します
  • base64url エンコード:標準の base64 を使用し、その後 + → -、/ → _ に置換し、末尾の = を削除します
  • code_verifier は sessionStorage に保存します(localStorage ではありません)。タブを閉じた後にベリファイアが残ったり、他のタブに漏れたりするのを防ぐためです(セキュリティ上、ベリファイアは一度限りのシークレットです)
ステップ 2:OpenRouter にリダイレクトする
https://openrouter.ai/auth?callback_url={url}&code_challenge={challenge}&code_challenge_method=S256
パラメーター値
callback_urlアプリの URL(認証後にユーザーが戻る場所)
code_challengeステップ 1 の S256 チャレンジ
code_challenge_method常に S256
ステップ 3:リダイレクトから戻ったときに処理する

ユーザーは ?code= が付加された状態で callback_url に戻ります。code クエリパラメーターを抽出します。

重要: ?code= を処理する前に、sessionStorage に code_verifier が存在することを確認してください。他のルートやサードパーティーのコードが、無関係な目的で ?code= クエリパラメーターを使用する場合があります。hasOAuthCallbackPending() ガードを使うことで、OAuth フローに属するコードだけを処理できます。

ステップ 4:コードを API キーと交換する
POST https://openrouter.ai/api/v1/auth/keys
Content-Type: application/json

{
  "code": "<code from query param>",
  "code_verifier": "<verifier from sessionStorage>",
  "code_challenge_method": "S256"
}

→ { "key": "sk-or-..." }

交換の前後に、sessionStorage からベリファイアを削除します。

ステップ 5:キーを保存してクリーンアップする
  • key を localStorage に保存します
  • history.replaceState({}, "", location.pathname) で URL をクリーンアップし、?code= を削除します
  • タブ間の同期: API キーの localStorage エントリーに対する storage イベントを監視し、ユーザーのサインインまたはサインアウト時に他のタブを更新します

認証モジュールのリファレンス

完全な PKCE フローを実装する、すぐに使えるモジュールです。base64url エンコード、sessionStorage の処理、キー交換を誤るリスクを軽減します。

// lib/openrouter-auth.ts
const STORAGE_KEY = "openrouter_api_key";
const VERIFIER_KEY = "openrouter_code_verifier";

type AuthListener = () => void;
const listeners = new Set<AuthListener>();
export const onAuthChange = (fn: AuthListener) => { listeners.add(fn); return () => listeners.delete(fn); };
const notify = () => listeners.forEach((fn) => fn());

// Cross-tab sync: other tabs update when user signs in/out
if (typeof window !== "undefined") {
  window.addEventListener("storage", (e) => { if (e.key === STORAGE_KEY) notify(); });
}

export const getApiKey = (): string | null =>
  typeof window !== "undefined" ? localStorage.getItem(STORAGE_KEY) : null;

export const setApiKey = (key: string) => { localStorage.setItem(STORAGE_KEY, key); notify(); };
export const clearApiKey = () => { localStorage.removeItem(STORAGE_KEY); notify(); };

// Guard: only process ?code= if we initiated an OAuth flow in this tab
export const hasOAuthCallbackPending = (): boolean =>
  typeof window !== "undefined" && sessionStorage.getItem(VERIFIER_KEY) !== null;

function generateCodeVerifier(): string {
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

async function computeS256Challenge(verifier: string): Promise<string> {
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

export async function initiateOAuth(callbackUrl?: string): Promise<void> {
  const verifier = generateCodeVerifier();
  sessionStorage.setItem(VERIFIER_KEY, verifier);
  const challenge = await computeS256Challenge(verifier);
  const url = callbackUrl ?? window.location.origin + window.location.pathname;
  window.location.href = `https://openrouter.ai/auth?${new URLSearchParams({
    callback_url: url, code_challenge: challenge, code_challenge_method: "S256",
  })}`;
}

export async function handleOAuthCallback(code: string): Promise<void> {
  const verifier = sessionStorage.getItem(VERIFIER_KEY);
  if (!verifier) throw new Error("Missing code verifier");
  sessionStorage.removeItem(VERIFIER_KEY);
  const res = await fetch("https://openrouter.ai/api/v1/auth/keys", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: "S256" }),
  });
  if (!res.ok) throw new Error(`Key exchange failed (${res.status})`);
  const { key } = await res.json();
  setApiKey(key);
}

サインインボタン

クリック時に initiateOAuth() を呼び出すボタンコンポーネントを作成します。OpenRouter のロゴを含め、複数の視覚的なバリエーションを用意してください。

OpenRouter ロゴ SVG
<svg viewBox="0 0 401.4 293.7" fill="currentColor">
  <path d="M303.9475,17.19926c42.79734,0,77.48933,34.69327,77.48933,77.48933s-34.69199,77.48933-77.48933,77.48933l76.86166,76.86244c9.76367,9.76313,2.84903,26.45667-10.95697,26.45667h-220.88335c-71.32686,0-129.14889-57.82202-129.14889-129.14889S77.64197,17.19926,148.96884,17.19926h154.97866ZM148.96884,68.85881c-42.79607,0-77.48933,34.69327-77.48933,77.48933s34.69327,77.48933,77.48933,77.48933,77.48933-34.69327,77.48933-77.48933-34.69327-77.48933-77.48933-77.48933Z"/>
</svg>

fill="currentColor" はボタンのテキストカラーを継承します。代わりに公式ブランドカラーを使用するには、ライトモードではパープル、ダークモードではライムを適用します。

<svg viewBox="0 0 401.4 293.7" class="fill-[#7624F4] dark:fill-[#C8FF00]">...</svg>
モード塗りつぶし
ライト背景#7624F4
ダーク背景#C8FF00
バリエーション(Tailwind)

参照実装と一貫した外観にするための推奨クラスです。

バリエーションクラス
defaultrounded-lg border border-neutral-300 bg-white text-neutral-900 shadow-sm hover:bg-neutral-50
minimaltext-neutral-700 underline-offset-4 hover:underline
brandedrounded-lg bg-neutral-900 text-white shadow hover:bg-neutral-800
icondefault と同じ + aspect-square(ロゴのみ、テキストなし)
ctarounded-xl bg-neutral-900 text-white shadow-lg hover:bg-neutral-800 hover:scale-[1.02] active:scale-[0.98]
サイズ
サイズクラス
smh-8 px-3 text-xs
defaulth-10 px-5 text-sm
lgh-12 px-8 text-base
xlh-14 px-10 text-lg

すべてのバリエーションで使用するクラス:inline-flex items-center justify-center gap-2 font-medium transition-all cursor-pointer disabled:opacity-50

キーの交換中はローディングインジケーターを表示します。デフォルトのラベルは「OpenRouter でサインイン」です。

ダークモード

ダークモードをサポートするには、ダークバリアントを追加します。明るい背景を暗い背景(dark:bg-neutral-900 dark:text-white)に切り替え、branded/ctaではその逆(dark:bg-white dark:text-neutral-900)に切り替えます。


API キーの使用

const response = await fetch("https://openrouter.ai/api/v1/responses", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "openai/gpt-4o-mini",
    input: [{ type: "message", role: "user", content: "Hello!" }],
  }),
});

型安全な SDK アプローチ(callModel、ストリーミング、ツールの使用)については、openrouter-typescript-sdk スキルを参照してください。


リソース

コメント