🔗 OAuth
OAuth は Google、LINE、Twitter 向けのパブリックなブラウザー開始・コールバックルートを提供します。独立した OAuth サービスではなく、authService により合成されます。
はじめに
各プロバイダードライバーを作成し、そのミドルウェアを Express アプリに登録してから、authService をマウントします。プロバイダーに登録するコールバック URL は、SDK ルートと同じマウントプレフィックスを含める必要があります: /auth/oauth/google/callback、/auth/oauth/line/callback、または /auth/oauth/twitter/callback。Twitter を有効にする場合は、以下のルートマウントを使用してください。そのドライバーは、プレフィックスのない Twitter SDK パスに対してのみセッションミドルウェアをインストールします。
import express from 'express';
import {drivers, services} from '@nodeblocks/backend-sdk';
const app = express();
const googleOAuthDriver = drivers.createGoogleOAuthDriver(
'google-client-id',
'google-client-secret',
'https://app.example.test/auth/oauth/google/callback',
);
const twitterOAuthDriver = drivers.createTwitterOAuthDriver(
'twitter-client-id',
'twitter-client-secret',
'https://app.example.test/auth/oauth/twitter/callback',
'replace-with-a-session-secret',
);
const lineOAuthDriver = drivers.createLineOAuthDriver(
'line-client-id',
'line-client-secret',
'https://app.example.test/auth/oauth/line/callback',
);
twitterOAuthDriver.initialize(app);
googleOAuthDriver.initialize(app);
lineOAuthDriver.initialize(app);
app.use(
'/',
services.authService(
{identities, refreshtokens, onetimetokens},
{
authSecrets: {authEncSecret: 'replace-me', authSignSecret: 'replace-me'},
oauth: {allowedRedirectOrigins: ['https://client.example.test', 'http://localhost:3000']},
},
{googleOAuthDriver, twitterOAuthDriver, lineOAuthDriver},
),
);
| 構成 | デフォルト / ソース動作 | 効果 |
|---|---|---|
dataStores.identities | すべてのコールバックで必須 | プロバイダーアイデンティティを検索し、oauth-signup 用に作成します。 |
dataStores.refreshtokens | authService で必須 | 生成されたリフレッシュトークンを保存します。 |
dataStores.onetimetokens | すべてのコールバックで必須 | 生成されたログイン用ワンタイムトークンを保存します。 |
configuration.authSecrets | 開始とコールバックで必須 | プロバイダー state に署名し、Google/LINE コールバックで検証します。最終ワンタイムトークンにも署名します。 |
configuration.oauth.allowedRedirectOrigins | インターフェースでは任意。デフォルトの許可リストなし。開始・コールバックのリダイレクト検証では空でない値が必要 | Google、LINE、Twitter のフロントエンドのリダイレクト先オリジンを許可します。未設定または空の場合はリダイレクトを拒否します。 |
configuration.onetimeTokenSignOptions | プロバイダー state または最終ログイントークンの生成に渡されます。authService のデフォルトは { expiresIn: '5m' } | ワンタイムトークンの有効期間を制御します。 |
| プロバイダードライバー | その 2 つのルートで必須 | request と callback を提供します。リクエスト前に initialize(app) を呼び出します。 |
Google と LINE の開始 state には、fp、purpose、redirectUrl、省略可能な typeId、ユーザーエージェントが含まれます。Twitter は、プロバイダーのコールバック state を管理するドライバーに { purpose, redirectUrl, typeId } を渡します。stateQueryParameter はエクスポートされていますが、SDK はコールバッククエリをスキーマで検証しません。
authService の第 2 引数の oauth.allowedRedirectOrigins に、フロントエンドのリダイレクト先のオリジンを設定します。例えば https://client.example.test/oauth/complete には https://client.example.test を指定します。プロバイダードライバーのコールバック URL はバックエンドを指し、別途設定します。パスやワイルドカードを含まない明示的なオリジンを使用してください。照合規則は設定リファレンスと validateURL を参照してください。
開始ルートとコールバックルートは、どちらもリダイレクト先のオリジンを検証します。オリジンが未設定または空の場合は AuthenticationUnexpectedError が発生し、両方のルートが 500 に対応付けます。ルートの仕様を参照してください。
よくあるタスク
| タスク | 開始地点 | 契約 |
|---|---|---|
| Google ログインまたはサインアップを開始する | googleOAuthFeature | パブリックな googleOAuthFeature と googleOAuthRoute。クエリ値は googleOauthSchema に従います。 |
| LINE ログインまたはサインアップを開始する | lineOAuthFeature | パブリックな lineOAuthFeature と lineOAuthRoute。クエリ値は lineOauthSchema に従います。 |
| Twitter ログインまたはサインアップを開始する | twitterOAuthFeature | パブリックな twitterOAuthFeature と twitterOAuthRoute。クエリ値は twitterOauthSchema に従います。 |
| プロバイダーからの戻りを処理 | OAuth コールバック機能 | state の redirectUrl を configuration.oauth.allowedRedirectOrigins に対して検証し、許可された URL に ?onetimeToken=<token> を付けてリダイレクトします。 |
| リダイレクト先のオリジンを検証 | blocks.validateURL | 絶対 URL のオリジンが許可されていれば ok(true) を返します。オリジン未設定、不正な URL、許可されていないオリジンにはエラーを返します。 |
ブラウザーワークフロー
GET /auth/oauth/google?fp=device-fingerprint&purpose=oauth-login&redirectUrl=https%3A%2F%2Fclient.example.test%2Foauth%2Fcomplete に移動します。開始ハンドラーは、まずリダイレクト URL を configuration.oauth.allowedRedirectOrigins に対して検証します。不正または許可されていない宛先は、ドライバーを呼び出す前に 400 を返します。許可された宛先では、Google ドライバーがブラウザーを Google にリダイレクトします。oauth-signup では typeId を指定してください。スキーマ上は任意ですが、未指定の場合、アイデンティティの作成は 400 で拒否されます。
絶対 URL の宛先をクエリシリアライザーに渡し、クエリパラメーターとして 1 回だけエンコードします。
const params = new URLSearchParams({
fp: 'device-fingerprint',
purpose: 'oauth-login',
redirectUrl: 'https://client.example.test/oauth/complete',
});
const loginUrl = `/auth/oauth/google?${params.toString()}`;
宛先を URLSearchParams に渡す前に encodeURIComponent を呼び出さないでください。二重エンコードすると、リクエストのクエリ解析後も URL 全体がエンコードされたままとなり、絶対 URL の検証に失敗します。SDK 0.15.0 は、最終的なトークン付きリダイレクト URL の生成時にもパーセントエンコーディングを保持します。
プロバイダー認証後、Google と LINE は state を受け取り、Twitter はドライバーが管理するコールバック state を受け取ります。state の取得後、コールバックは validateURL を使い、state の redirectUrl を configuration.oauth.allowedRedirectOrigins に対して検証します。URL は絶対 URL で、オリジンが許可されている必要があります。不正または許可されていない URL は 400、オリジンが未設定または空の場合は 500 を返します。検証に失敗すると、SDK によるアイデンティティの検査・検索・作成、トークンの生成・保存、最終リダイレクトより先に処理が停止します。Google はサインアップ時のメールアドレス重複検査より前、LINE は state の抽出後、Twitter はドライバーが state とプロフィールを返した後に検証します。
検証に成功すると、コールバックはアイデンティティを検索または作成し、ワンタイムトークンを生成・保存して、state の redirectUrl に onetimeToken を追加した 302 リダイレクトを返します。generateRedirectURL は、パス内の %20 など、指定された URL 文字列とそのパーセントエンコーディングを保持し、追加の URL デコードを行いません。リダイレクトヘルパーは常に ?onetimeToken= を追加するため、呼び出し側はそのクエリ文字列の動作を許容できるリダイレクト URL を指定する必要があります。
カスタム機能の合成
サービスが通常の統合ポイントです。カスタムホストでは、同じストア、構成、プロバイダードライバーを合成コンテキストで渡す必要があります。機能は Express ミドルウェアではなく SDK コンポーザーです。
以下の configuration にも、上記のサービス設定と同様に authSecrets と、開始・コールバックのリダイレクト検証用の空でない oauth.allowedRedirectOrigins 許可リストが必要です。
import {partial} from 'ramda';
import {features, primitives} from '@nodeblocks/backend-sdk';
const router = primitives.defService(
partial(primitives.compose(features.googleOAuthFeature, features.googleOAuthCallbackFeature), [
{configuration, dataStores: {identities, onetimetokens}, googleOAuthDriver},
]),
);
app.use('/', router);
リファレンスマップ
| ページ | 目的 |
|---|---|
| ブロック | プロバイダー state、トークン、フィンガープリント、リダイレクトのヘルパー、および validateURL によるオリジン検証。 |
| 機能 | スキーマからルートへの OAuth 合成。 |
| ルート | パブリックな開始・コールバックエンドポイントの契約。 |
| スキーマ | プロバイダー開始クエリの検証。 |
OAuth には handlers.md や validators.md ページはありません。Authentication を介して合成されます。
関連モジュール
Authentication は、これを含むサービスとワンタイムトークンログインを提供します。Authentication サービス は構成とマウントを定義します。OAuth ドライバー は Google、Twitter、LINE のドライバーファクトリーとミドルウェアを定義します。Authentication ブロック はトークン、フィンガープリント、アイデンティティのヘルパーを提供し、共通ブロック は最終リダイレクトを提供します。