SSOとOAuth認証
PotatoでGoogle、GitHub、HuggingFace、汎用OIDCによるログインを設定します。メールドメインやGitHub組織でアクセスを制限し、SSOとローカルのパスワードログインを併用できます。
v2.3.0の新機能
デフォルトでは、Potatoはシンプルなユーザー名ベースのログインを使用し、アノテーターは任意のユーザー名を入力してサインインします。これはローカル開発や小規模プロジェクトには十分ですが、本番環境のデプロイメントでは、不正アクセスの防止、アノテーターの身元確認、組織の要件への準拠のために適切な認証が必要です。
SSOの設定は1か所にまとまっています。authentication.methodをoauthに設定し、authentication.providersの下に1つ以上のプロバイダーを列挙します。各プロバイダーには、ログインページ上に専用のボタンが表示されます。
Potatoには、google、github、huggingfaceの3つのプロバイダーについてエンドポイントのメタデータが組み込まれています。それ以外のプロバイダー名は汎用OIDCとして扱われ、discovery_urlが必要です。
クイックスタート
最小構成のGoogle設定:
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
user_identity_field: "email"
secret_key: ${POTATO_SECRET_KEY}シークレットはファイルではなく環境変数で設定します:
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret"
export POTATO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"secret_keyはトップレベルのキーであり、authenticationブロックには含まれません。これを設定するか、POTATO_SECRET_KEYを設定してください。どちらもない場合、Potatoはランダムなキーを生成するため、再起動のたびにすべてのセッションが無効になります。また、これを設定せずにOAuthを構成するとPotatoは警告をログに出力します。
PotatoはOAuth用に2つのルートを提供します。/auth/login/<provider>がフローを開始し、/auth/callback/<provider>がレスポンスを受け取ります。コールバックはプロバイダーに登録するURLなので、Googleの場合はhttps://your-domain.com/auth/callback/googleです。redirect_uriという設定キーはなく、ルートはプロバイダー名から決まります。
Google Cloud Consoleで種類が"Web application"のOAuth 2.0認証情報を作成し、承認済みリダイレクトURIとしてhttps://your-domain.com/auth/callback/googleを追加します。ローカル開発ではhttp://localhost:8000/auth/callback/googleも受け付けられます。
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
allowed_domain: "umich.edu"allowed_domainは単一のドメイン文字列を取ります。メールアドレスがそのドメインに属するアカウントだけがサインインできます。
GitHub
GitHub Settings > Developer settings > OAuth AppsでOAuthアプリを登録し、コールバックURLをhttps://your-domain.com/auth/callback/githubに設定します。
authentication:
method: "oauth"
providers:
github:
client_id: ${GITHUB_CLIENT_ID}
client_secret: ${GITHUB_CLIENT_SECRET}
allowed_org: "my-research-lab"allowed_orgは単一の組織名を取ります。これを設定するとPotatoはread:orgスコープを自動的に要求するため、スコープを自分で列挙する必要はありません。チェックが所属を確認できるよう、アノテーターは組織のメンバーシップを公開にしておく必要があります。
HuggingFace
huggingface.co/settings/applicationsでOAuthアプリケーションを作成します。
authentication:
method: "oauth"
providers:
huggingface:
client_id: ${HF_CLIENT_ID}
client_secret: ${HF_CLIENT_SECRET}
user_identity_field: "username"HuggingFaceプロバイダーはHuggingFaceのOIDCディスカバリーエンドポイントに接続し、デフォルトでopenid profile emailを要求します。
汎用OIDC
google、github、huggingface以外のプロバイダー名は汎用OIDCとして扱われ、discovery_urlが必要です。ログインボタンの表記がわかりやすくなるよう、display_nameを指定してください。
authentication:
method: "oauth"
providers:
oidc:
display_name: "University SSO"
discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
client_id: ${OIDC_CLIENT_ID}
client_secret: ${OIDC_CLIENT_SECRET}
scopes:
- "openid"
- "email"
- "profile"プロバイダーに登録するコールバックURLはプロバイダーキーに従うため、oidcという名前のブロックではhttps://your-domain.com/auth/callback/oidcを使います。
主なプロバイダーのディスカバリーURL:
| プロバイダー | ディスカバリーURL |
|---|---|
| Okta | https://your-org.okta.com/.well-known/openid-configuration |
| Azure AD | https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration |
| Auth0 | https://your-domain.auth0.com/.well-known/openid-configuration |
| Keycloak | https://keycloak.example.com/realms/{realm}/.well-known/openid-configuration |
scopesのデフォルトは["openid", "email", "profile"]です。
複数のプロバイダー
providersには必要なだけエントリを追加でき、それぞれに専用のログインボタンが表示されます。
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
github:
client_id: ${GITHUB_CLIENT_ID}
client_secret: ${GITHUB_CLIENT_SECRET}
oidc:
display_name: "University SSO"
discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
client_id: ${OIDC_CLIENT_ID}
client_secret: ${OIDC_CLIENT_SECRET}user_identity_field: "email"を指定すると、Googleでサインインし、後に同じアドレスで大学のプロバイダーからサインインした人は同じPotatoユーザーとなり、同じ割り当てに戻ります。
アイデンティティと自動登録
user_identity_fieldは、プロバイダーのどのクレームをPotatoのユーザー名にするかを決めます。email、username、sub、nameのいずれかを受け付け、デフォルトはemailです。アノテーターの出力ディレクトリはユーザー名にちなんで命名されるため、この値は重要です。研究の途中で変更すると、アノテーターがそれまでの作業から切り離されます。
auto_registerは、初回のログイン成功時にPotatoアカウントを作成するかどうかを制御します。デフォルトはtrueです。
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
allowed_domain: "umich.edu"
user_identity_field: "email"
auto_register: trueアクセス制御は、設定ファイル内のアドレスリストではなくプロバイダーを通じて行います。サインインできるユーザーを制限するには、Googleではallowed_domain、GitHubではallowed_org、あるいはOIDCプロバイダー側のグループやアプリ割り当ての機能を使います。
混合モード
allow_local_loginを有効にすると、SSOボタンの下にユーザー名とパスワードのフォームが追加されます。デフォルトはfalseなので、有効にしない限りSSOがローカルログインに置き換わります。
require_password: true
user_config:
allow_all_users: true
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
allow_local_login: true移行時にはしばらく両方を併用します。アノテーターをプロバイダーへ移行させ、全員がプロバイダー経由でサインインしたらallow_local_loginを削除します。混合モードは、組織内のアノテーターと、あなたの組織にアカウントを持たない外部のアノテーターを組み合わせる研究にも使えます。
混合モードのローカルアカウントは、他のパスワードアカウントと同じソルト付きPBKDF2ハッシュを使います。リセット手順についてはパスワード管理を参照してください。
セッション
セッションの有効期間はトップレベルのキーで、単位は日です:
session_lifetime_days: 7再起動後もセッションが維持されるのは、secret_keyが固定されている場合です。
管理者アクセス
管理用エンドポイントは、OAuthブロックではなくAPIキーで認証します:
admin_api_key: ${POTATO_ADMIN_API_KEY}Potatoはadmin_api_keyをまず設定ファイルから、次に環境変数POTATO_ADMIN_API_KEYから読み取ります。どちらも設定されていない場合はキーを生成し、タスクディレクトリ内のadmin_api_key.txtに書き込みます。呼び出し側はX-API-Keyヘッダーでこのキーを送ります。
HTTPS
本番環境では、OAuthプロバイダーはHTTPSのリダイレクトURIを要求します。localhostは例外なので、HTTPでの開発は証明書なしで動作します。
リバースプロキシの背後では元のスキームを転送してください。転送しないと、Potatoが組み立てるコールバックURLがhttpになり、プロバイダーに拒否されます:
location / {
proxy_pass http://localhost:8000;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}設定リファレンス
| キー | 型 | デフォルト | 説明 |
|---|---|---|---|
authentication.method | string | in_memory | in_memory、database、clerk、oauthのいずれか |
authentication.providers | dict | oauthでは必須 | プロバイダーごとに1エントリ、プロバイダー名をキーとする |
authentication.user_identity_field | string | email | email、username、sub、nameのいずれか |
authentication.allow_local_login | bool | false | SSOボタンと並べてパスワードフォームを表示する |
authentication.auto_register | bool | true | 初回ログイン時にPotatoアカウントを作成する |
secret_key | string | ランダム | トップレベル。セッションを永続化するには固定値が必要 |
session_lifetime_days | int | 7 | トップレベルのセッション期間 |
admin_api_key | string | 自動生成 | 管理用エンドポイントのためのトップレベルのキー |
プロバイダーごとのキー:
| キー | 型 | 必須 | 説明 |
|---|---|---|---|
client_id | string | はい | プロバイダーから取得 |
client_secret | string | はい | プロバイダーから取得 |
discovery_url | string | 汎用OIDCでは必須 | OIDCディスカバリーエンドポイント |
display_name | string | いいえ | ログインボタンのテキスト |
allowed_domain | string | いいえ | 1つのメールドメインに制限する |
allowed_org | string | いいえ | 1つのGitHub組織に制限する |
scopes | list | いいえ | デフォルトは["openid", "email", "profile"] |
完全な例
annotation_task_name: "Annotation Project"
task_dir: "."
authentication:
method: "oauth"
providers:
google:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
allowed_domain: "umich.edu"
user_identity_field: "email"
auto_register: true
secret_key: ${POTATO_SECRET_KEY}
session_lifetime_days: 7
admin_api_key: ${POTATO_ADMIN_API_KEY}
data_files:
- "data/instances.jsonl"
item_properties:
id_key: id
text_key: text
annotation_schemes:
- annotation_type: radio
name: sentiment
description: "What is the sentiment of this text?"
labels: [Positive, Neutral, Negative]
output_annotation_dir: "output/"
export_annotation_format: "jsonl"トラブルシューティング
プロバイダーがリダイレクトURIを拒否する。 プロバイダーに登録したURIは/auth/callback/<provider>と完全に一致する必要があります。スキームや末尾のパスも照合の対象になるため、X-Forwarded-Protoを転送しないプロキシではhttpのコールバックが生成され、プロバイダーに拒否されます。
再起動後に全員がログアウトされる。 secret_keyが設定されていないため、Potatoが起動時に新しいキーを生成しています。
GitHub組織による制限でメンバーが拒否される。 そのメンバーの組織への所属が非公開になっています。本人が公開にするか、allowed_orgを外して別の方法でアクセスを制御してください。
検証が"providers is required"で失敗する。 method: "oauth"にはprovidersの下に少なくとも1つのエントリが必要です。methodの値はoauthであり、プロバイダー名ではありません。
参考資料
- パスワード管理 -- ローカルアカウント、ハッシュ化、リセット手順
- パスワードレスログイン -- オープンなタスク向けのユーザー名のみのアクセス
- クラウドソーシング統合 -- クラウドソーシングプラットフォームの認証
- 管理ダッシュボード -- ユーザーと権限の管理
実装の詳細については、ソースドキュメントを参照してください。