Crowi

外部アカウントでのサインイン

Google などの OAuth2 / OIDC プロバイダを使ったサインインの設定

Crowi は認証プロバイダをプラグインとして追加できます。@crowi/plugin-google を有効にすると、サインイン画面に「Google でサインイン」ボタンが現れ、Google アカウントで Crowi にサインインできるようになります。

Google は特別扱いされていません。プロトコル部分 (OIDC discovery、state / PKCE / nonce、トークン交換、ID トークン検証) はすべて Crowi 本体が持っていて、プラグインが持つのは「どこを discovery するか」「どのスコープを要求するか」「ボタンを何と呼ぶか」の 3 つだけです。同じ仕組みで他の OIDC プロバイダのプラグインも書けます。

前提となる環境変数

外部サインインのリダイレクト URI は、リクエストの Host ヘッダからではなく設定された公開 URL から組み立てられます。したがって web の公開 origin を Crowi に教える必要があり、AUTH_PUBLIC_WEB_URL か、その既定値として使われる CLIENT_URL のどちらかを設定します。API が別 origin で応答する場合は AUTH_PUBLIC_API_URL も設定します。各変数の行は 環境変数 にあります。

どちらの変数も origin のみ (パス・クエリ・フラグメントを含まない) である必要があり、含めるとブート時に失敗します。CLIENT_URL が既定値として使われるのも、それが origin のみのときだけです。web の公開 origin がどこからも決まらない場合、外部サインインは静かに無効化されます — ブートは成功し、プロバイダ一覧が空になり /api/auth/providers/<name>/start が 404 を返します。ボタンが出ないときはまずここを疑ってください。

クライアントシークレットを暗号化して保存するために 機密設定の暗号化 (CROWI_ENCRYPTION_KEY) も強く推奨します。未設定でも動作しますが、シークレットは平文で DB に保存されます。

セットアップ

1. プラグインを有効化する

runner プロジェクトの package.json の依存に @crowi/plugin-google を追加し、crowi.config.jsonplugins 配列に列挙します。

{
  "plugins": ["@crowi/plugin-google"]
}

2. Google Cloud で OAuth クライアントを作る

Google Cloud Console で「OAuth 2.0 クライアント ID」を ウェブアプリケーション として作成し、承認済みのリダイレクト URI に次を登録します。

<AUTH_PUBLIC_API_URL>/api/auth/providers/google/callback

たとえば AUTH_PUBLIC_API_URL="https://wiki.example.com" なら https://wiki.example.com/api/auth/providers/google/callback です。ここが 1 文字でも違うと Google 側で redirect_uri_mismatch になります。

3. 管理画面に認証情報を入力する

管理画面の プラグイン 設定で Google プラグインを開き、Client ID と Client secret を入力して保存します。再起動は不要で、保存した時点でサインイン画面にボタンが現れます。

Client ID と secret は 2 つで 1 つの設定 として単一のドキュメントに保存されます。片方だけが保存された状態は発生せず、片方だけを入力した場合はプロバイダが「未設定」のままになるので、必ず失敗するボタンがサインイン画面に出ることはありません

サインイン時の挙動

初めて Google でサインインしたユーザーの扱いは、管理画面登録モード (registrationMode) の設定に従います。

登録モード未登録ユーザーが Google でサインインしたとき
Openユーザー名を選ぶ画面を経て、アカウントが作成され即座に有効になる
Restrictedユーザー名を選ぶ画面を経てアカウントが作成され、管理者の承認待ちになる
Closedサインインを拒否し、登録が閉じている旨をサインイン画面に表示する

いくつかの重要な制約があります。

  • メールアドレスは Google が verified と主張した場合のみ使われます。未検証のメールアドレスは受け付けません。
  • 既存のローカルアカウントと同じメールアドレスでも自動的には紐付きません。 その場合はサインインがエラーになります。パスワードでサインインしてから、設定画面で明示的にアカウントを連携してください。これは、メールアドレスの一致だけで既存アカウントの支配権を渡さないための意図的な設計です。

アカウントの連携と解除

サインイン済みのユーザーは、設定 → パスワード/APIトークン/アカウント連携 から、自分のアカウントに外部プロバイダを紐付けたり外したりできます。利用者側の操作手順は アカウントとセキュリティ を参照してください。

運用者が知っておく必要があるのは次の 3 点です。

  • 連携は同一サイトのみに対応します。 web と API を別 origin に置く構成でも、両方が同じ registrable domain にある限り動作します。まったく別ドメイン間で third-party cookie が必要になる構成には対応していません。
  • 複数レプリカ構成では REDIS_URL が必須です。 連携の途中経過をレプリカ間で共有できないと連携が完了しないため、この要件は fail-closed です。共有 Redis が無いまま複数レプリカを宣言していると、連携の開始時点でエラーになります (黙ってレプリカ間で共有できないストアにフォールバックすることはありません)。単一レプリカ構成ではプロセス内に保持するため、連携の途中で api を再起動するとその連携は続行できません。
  • 利用者自身の連携解除には、そのアカウントがパスワードでサインインできる状態であることが必要です。 パスワード未設定のユーザーや、インスタンス全体でパスワードサインインを無効にしている場合は解除できません。復旧は管理者の解除で行います (下記)。

管理者から見た連携

管理画面のユーザー一覧では、連携しているユーザーの行にアイコンが出ます。行メニューから管理者が連携を解除することもできます。ただし条件は本人が行う場合とは異なります。本人の解除はパスワード未設定なら拒否されますが、管理者の解除は対象にパスワードが無ければ発行してから解除し、発行した平文を一度だけ表示します。乗っ取られたプロバイダアカウントからの復旧が管理者解除の目的だからです。既にパスワードがある場合はそのまま残ります (解除自体は、本人が知っているパスワードを無効化する理由にならないため)。

拒否されるのは 自分自身を解除しようとした場合 と、インスタンス全体でパスワードサインインが無効な場合 の 2 つで、どちらも 409 を返します。

連携済みアカウントのメールアドレスは、管理者からも変更できません。 別のアドレスを指定すると 409 EMAIL_LOCKED_BY_FEDERATED_IDENTITY で拒否されます。連携済みアカウントは crowi の外からサインインできるため、アドレスを差し替えると確認リンクの宛先 — つまりアカウントの復旧手段 — が持ち主から離れてしまうからです。ユーザー自身の設定画面でも同じ理由で固定されています (ユーザーページとプロフィール 参照)。

うまくいかないとき

サインイン画面にボタンが出ない AUTH_PUBLIC_WEB_URL (または CLIENT_URL) が設定されているか、Client ID と secret の両方が保存されているかを確認してください。どちらが欠けていても、プロバイダは一覧に現れません。

Google 側で redirect_uri_mismatch になる Google Cloud Console に登録したリダイレクト URI と、AUTH_PUBLIC_API_URL から組み立てられる URI が完全に一致しているか確認してください。リバースプロキシ配下では、AUTH_PUBLIC_API_URLプロキシの外側の originを設定する必要があります。

「そのメールアドレスは既に登録されています」と表示される そのメールアドレスのローカルアカウントが既に存在します。パスワードでサインインし、設定画面から Google アカウントを連携してください。

On this page