Discourse に新しい「managed」認証方法を追加する

Future Social Authentication Improvements… からの続きです。

現在、すべての「関連付けられたアカウント」情報を単一のデータベーステーブルに移行する作業を進めています。これにより、重複するロジックが大幅に削減され、将来的な開発が迅速化されます。例えば、コアのTwitterロジックを移行したことで、コード行数が136行からわずか24行に削減されました :tada:

この記事は、新しい認証プロバイダーを追加するためのステップバイステップの手順書として設計されたものではありませんが、必要な場合に適切なソースコードを参照しながら、概要を説明することを目的としています。

オーセンティケータの実装

各オーセンティケータは、必ず Auth::Authenticator のサブクラスを実装しなければなりません。新しい共有ロジックを使用する場合、オーセンティケータは Auth::ManagedAuthenticator を継承できます。基本的な実装の例は、コアのFacebookオーセンティケータで見つけることができます。

実装クラスは、nameenabled?、および register_middleware をオーバーライドする必要があります

:information_source: 補足: マルチサイト互換性のために、サイト固有の情報を認証時に固定するのではなく、setup ラムダを使用してomniauthに提供することが重要です。これについては、すべてのコアオーセンティケータの例を参照してください。

外部アカウントをDiscourseアカウントにリンクするためのすべてのロジックは、Auth::ManagedAuthenticator によって処理されます。これは、omniauthプロバイダーがそのドキュメントで定義されている形式でデータを返すことに依存しています。このデータの操作が必要な場合、オーセンティケータは after_authenticate メソッドをオーバーライドし、必要に応じてauth_tokenを操作できます。例えば、コアのTwitterオーセンティケータは、トークンからすべての extra 情報を削除します。

データは user_associated_accounts データベーステーブルに保存されます。provider_uidinfocredentials、および extra はすべて、omniauthから返されたデータから直接取得されます。

Authenticator クラスが定義されたら、それを登録する必要があります。これはアプリケーションのライフサイクルの早い段階で行う必要があり、プラグインの after_initialize メソッド内ではできません。最小限の登録には、オーセンティケータへの参照を含めるだけで済みます。プラグインでは、auth_provider 関数を使用して登録できます。例えば:

auth_provider authenticator: OpenIDConnectAuthenticator.new()

コアでは、登録は discourse.rb で行われます。使用可能な AuthProvider オプションの完全なリストは、こちらで確認できます。テキストコンテンツはこれらのオプションを使用して定義できますが、標準キーに従って client.en.yml にローカライズ可能な文字列を提供することが推奨されます。例えば:

@fantasticfears による ManagedAuthenticator の追加メモ

ManagedAuthenticator の詳細

認証に関して特別な作業を行う必要があるかもしれません。そして、ManagedAuthenticator についてもっと知りたいでしょう。基本的に、これにはいくつかの操作、オプションがあり、データの使用方法を制御します。

Discourse は、2つのコントローラーでユーザー情報を管理します。Users::OmniauthCallbacksController は、OAuth2 認証が完了した後のペイロードを管理します。ここで after_authenticate が呼び出されます。can_connect_existing_user? もここで使用されます。
異なるデータフィールドがどのように機能するかを理解するために、読み取ることができるプライベートメソッドがいくつかあります。

if authenticator.can_connect_existing_user? && current_user
  @auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
else
  @auth_result = authenticator.after_authenticate(auth)
end

UsersController には、can_revoke?revoke を使用する revoke_account があります。ただし、revoke メソッドをリモートで機能させるには、独自の実装を構築する必要があります。

UserAuthenticator は、ユーザーの認証(メール確認またはOAuth2パスの検証)を支援するサービスクラスです。ここで after_create_account が呼び出されます。

コアロジックは、Auth::Result データクラスを伴う after_authenticate に残ります。ここではデータ構造に従います。extra_data は、関連レコードを作成するために after_create_account に渡されます。

result.extra_data = {
  provider: auth_token[:provider],
  uid: auth_token[:uid],
  info: auth_token[:info],
  extra: auth_token[:extra],
  credentials: auth_token[:credentials]
}

既存のアカウントとの照合と接続を試みます。
自動アカウント作成が可能であるのに User.create がないのはなぜか疑問に思うかもしれません。これは UsersController#create で行われます。

authentication = UserAuthenticator.new(user, session)

ユーザーは、認証プロバイダーによって準備されたセッションデータで入力される新しいインスタンスです。信じてください、これは単なる魔法です。


新しいシステムへの移行

新しいシステムへのシームレスな切り替えを提供するために、データは古い保存場所から移行される必要があります。コア認証プロバイダーの場合、これは専用のテーブルである可能性があります。プラグインの場合、これは plugin_store_rows または oauth2_user_infos である可能性があります。user_associated_accounts 行に必要な最小限のデータは、provider_nameprovider_uid、および user_id です。移行の例については、以下を参照してください。

ManagedAuthenticator システムが v2.2.0 で安定ブランチにリリースされると、公式認証プラグインの移行を開始します。この時点で、plugin_store_row の移行例がここに追加されます。


このドキュメントはバージョン管理されています - 変更の提案はgithubで行ってください。

「いいね!」 23

@david ここで行われたすべての作業は非常にクールです。大変感謝しています。また、非常に便利な GitHub - discourse/discourse-development-auth: A discourse plugin which adds a fake authentication provider. For development purposes only. も試す機会がありました。

ただ一つ注意点があります。この機能はローカルのEmber CLIとうまく連携しません(サインアップポップアップが表示されません)。プラグインを追加して認証プロバイダーを追加しようとしているときに頭を悩ませていましたが、NO_EMBER_CLI=1 を使用することを思いつき、すべてが機能し始めました。

「いいね!」 7

認証機能の実装が、

への正しいアプローチかどうかを知りたいです。

登録されているすべての認証機能がアプリの早い段階で呼び出されるため、URLにユーザー名とメール認証のヒントが含まれているかどうかをそこでテストし、「ログインリンクを送信」フォームを応答としてレンダリングできる、と理解して合っていますか?