Hosted Login#

Hosted Login は Environment の Interaction Domain 上で動作する Zeroword の認証画面である。OIDC/OAuth 2.0 の認可フローを利用する場合に使い、独自のログイン画面を構築する場合は Public Authentication API を使う。

認可フロー#

  1. アプリは Environment の Canonical Issuer にある GET /e/{environmentId}/oauth2/authorize へ OIDC 認可リクエストを送る。Canonical Issuer と Interaction Domain は別の host である。エンドポイント、PKCE、callback の条件は OIDC / OAuth 2.0 を参照する。
  2. 認可リクエストが有効なら、Authorization Server は短命の認証 transaction を作成し、Interaction Domain の / へ tx、exp、mac を付けて 302 redirect する。この handoff は transaction を Hosted Login へ渡すためのもので、認証処理は Interaction Domain 上で行う。
  3. Hosted Login は GET /v1/auth/transactions/{transactionId} で状態と available_methods を読み、theme の methods の順を維持しつつ利用可能な方式だけを表示する。方式に応じて Public Authentication API の Email OTP、Passkey、Social login の transaction endpoint を呼び出す。
  4. 認証後、Hosted Login は POST /v1/auth/transactions/{transactionId}/complete を呼び、返された redirect_uri に code と元の state を付けてアプリへ戻る。アプリは通常の OIDC code exchange を行う。

Hosted Login の画面と認証方式#

ログイン画面に表示される方式は、Environment で利用可能な方式と theme の methods の積集合である。methods は表示順も指定する。

方式 Hosted Login の動作 有効になる条件
email_otp メールアドレスを入力し、メールで受け取った OTP を入力する。 常に利用可能な基本方式で、theme の methods に含める。
passkey Passkey の sign-in prompt からブラウザーの WebAuthn UI を開始する。Email OTP 認証の直後には Passkey 登録 prompt も表示され、登録を後回しにできる。 常に利用可能な基本方式で、theme の methods に含める。既定 RP ID が retired になった後は既定 host の available_methods から除外される。
Social login 有効な provider ごとのボタンを表示し、provider の認証画面へ遷移する。google、apple、github、microsoft、generic_oidc を扱う。 Management API で Social connection が active であること。generic_oidc はさらに owner_confirmed が必要である。方式を theme の methods に含める。
既存アカウントとの連携 Social callback が既存アカウントとの連携を必要とする場合、対象 provider と既存方式で再認証する説明画面を表示する。利用者がログインを続け、既存の方式で認証した後に pending identity が連携される。メールアドレス一致だけで自動連携はしない。 Social provider と既存アカウントの状態によって transaction が link_required になった場合。

方式の有効化は /environments/{environmentId}/settings では行わない。この endpoint が現在提供する設定は impersonation_enabled のみであり、ログイン方式を切り替える項目はない。passkey と email_otp は基本方式として利用可能で、Social login は active な Social connection から導出される。画面に出すかどうかと順序は theme の methods で制御する。Social connection の設定は Management API を参照する。

Locale#

Hosted Login は ja と en に対応する。Interaction Domain の / に有効な locale=ja または locale=en があればそれを使い、それ以外または未指定なら theme の defaultLocale を使う。通常の /authorize redirect は transaction handoff の tx、exp、mac を設定するが、認可リクエストの locale は引き継がないため、標準フローの locale は defaultLocale で決まる。

Theme の設定#

Theme は Environment ごとの Hosted Login 外観と表示方式を指定する。正規の JSON Schema は specs/theme.schema.json である。許可されるプロパティは次のとおり。

プロパティ 許可値
logoUrl 任意。HTTPS URL。
appearance.primaryColor, backgroundColor, textColor #rgb、#rrggbb、#rrggbbaa 形式の hex color。
appearance.borderRadius 0–24 の整数ピクセル。
appearance.fontFamily system、sans、serif、mono のいずれか。任意の外部 font は指定できない。
appearance.density comfortable または compact。
methods passkey、email_otp、google、apple、github、microsoft、generic_oidc の重複しない非空配列。配列順が画面の表示順になる。
defaultLocale ja または en。
termsUrl, privacyUrl, supportUrl 任意。HTTPS URL。画面の legal/support link に使う。

Theme は strict schema であり、上記以外のキー、任意 HTML、任意 JavaScript、inline event handler、script URL、data URL の SVG、任意 CSS は受け付けない。Theme に任意の文言を挿入する項目もない。色、数値、URL、enum は schema で制限され、値は CSS custom properties へ変換される。

Management API の theme endpoint は environments:read / environments:write scope を使う。GET は { environment_id, version, theme } と ETag を返す。PUT は theme オブジェクト全体を body に送る。If-Match は任意だが、指定する場合は直前の GET で返った ETag を使う。ETag が stale、または保存中に競合が起きた場合は 412 precondition_failed、schema 不正は 400 invalid_theme になる。If-Match を省略すると ETag による事前条件チェックは行われない。Preview endpoint は CSS custom properties を返す。

export MANAGEMENT_API=https://api.zeroword.smartcrab.ai/v1
export AUTH_PLATFORM_API_KEY='<api-key>'
export ENVIRONMENT_ID='<environmentId>'

curl -sS -D theme.headers \
  -H "Authorization: Bearer ${AUTH_PLATFORM_API_KEY}" \
  "${MANAGEMENT_API}/environments/${ENVIRONMENT_ID}/theme"

curl -sS \
  -H "Authorization: Bearer ${AUTH_PLATFORM_API_KEY}" \
  "${MANAGEMENT_API}/environments/${ENVIRONMENT_ID}/theme/preview-css"

Theme の変更には、If-Match に直前の GET で取得した ETag を指定する条件付き更新を推奨する。body には上記 schema に適合する完全な theme オブジェクトを PUT /environments/{environmentId}/theme へ送る。GET /environments/{environmentId}/settings と PATCH /environments/{environmentId}/settings は impersonation_enabled を読み書きする別設定 API である。

Custom Domain と Passkey の RP ID#

Custom Domain は Management API の /domains で Environment に登録する。登録直後の status は pending であり、DNS と証明書の検証が完了して active になるまで、その host は有効な Interaction Domain として受理されない。

curl -sS -X POST \
  -H "Authorization: Bearer ${AUTH_PLATFORM_API_KEY}" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <unique-key>' \
  --data '{"environment_id":"<environmentId>","hostname":"auth.customer.example","rp_enabled":true}' \
  "${MANAGEMENT_API}/domains"

POST /domains は 201 と Domain object を返す。response には id、environment_id、hostname、status、rp_enabled、validation_errors、created_at、updated_at、activated_at が含まれる。status の値は pending、active、moved、failed、deleting であり、validation_errors は検証エラー文字列の配列、activated_at は有効化時刻(未有効なら null)である。

DNS 設定は response に含まれない。登録した hostname に次の CNAME を作成する。証明書は CNAME 経由の HTTP 検証で自動発行されるため、TXT record は不要である。

Type Name Target
CNAME auth.customer.example (登録した hostname) saas-fallback.zeroword.smartcrab.ai

DNS 事業者が proxy 機能を持つ場合は無効 (DNS only) にする。CNAME の反映後に POST /domains/{domainId}/retry-validation を呼ぶと最新の検証状態を再確認し、結果に応じて Domain の status と validation_errors を更新する。GET /domains/{domainId} で状態を確認する。

rp_enabled: true の active な Custom Domain は、その Environment の Interaction Domain と WebAuthn RP ID の両方になる。1 Environment につき rp_enabled の Custom Domain は 1 つまでである。Production で Passkey を登録する前に Custom Domain を設定する。既定構成では Interaction Domain と RP ID はともに Default Interaction Domain であり、Custom Domain の有効化後はともにその hostname になる。

RP ID は Passkey credential に束縛されるため、Custom Domain の有効化後は旧 RP ID と新 RP ID の Passkey が併存する移行期間を設ける。利用者は旧 RP ID の Passkey(ブラウザーが WebAuthn Related Origin Requests に対応する場合)、または Email OTP / Social login で再認証し、新 RP ID 上で Passkey を登録する。旧 RP ID の Passkey を Custom Domain から直接使うには、旧 RP ID の /.well-known/webauthn が新しい origin を許可する必要がある。対応しない browser では Email OTP / Social login を使う。

移行状態は次で取得する。migrated_share は移行対象者のうち新 RP ID の Passkey を持つ利用者の割合であり、集計対象は失効していない Passkey である。

curl -sS \
  -H "Authorization: Bearer ${AUTH_PLATFORM_API_KEY}" \
  "${MANAGEMENT_API}/environments/${ENVIRONMENT_ID}/rp-migration"

POST /environments/{environmentId}/rp-migration/disable-legacy に {"confirm_rp_id":"<legacyRpId>"} を送ると旧 RP を無効化する。入力値は旧 RP ID と完全一致する必要がある。この操作は取り消せない。旧 RP を無効化した後は既定 host での passkey 操作が拒否され、rp_enabled domain の解除や削除も拒否される。移行を完了する前に無効化してはならない。

Native app association files#

Native app の Passkey 関連付けは Canonical Issuer ではなく Environment の Interaction Domain 上で取得する。

Platform Endpoint 生成元
iOS /.well-known/apple-app-site-association 登録済み mobile app の Apple Team ID と bundle ID。
Android /.well-known/assetlinks.json 登録済み mobile app の package name と SHA-256 certificate fingerprint。

未登録アプリは含まれない。両 endpoint は Cache-Control: public, max-age=300 と弱い ETag を返す。If-None-Match が現在の config_version に一致すれば body なしの 304 を返す。iOS は Associated Domains、Android は App Links / Credential Manager の設定で該当する Interaction Domain を使う。React Native SDK のセットアップは React Native SDK (@auth-platform/react-native) を参照する。

CSP#

HTML response には次の Content Security Policy が設定される。

default-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' https: data:;
connect-src 'self';
font-src 'self';
frame-ancestors 'none';
base-uri 'none';
form-action 'self';
object-src 'none';

Hosted Login HTML は次の security headers も返す。

Header Value
Cross-Origin-Opener-Policy same-origin
Cross-Origin-Resource-Policy same-origin
Referrer-Policy no-referrer
X-Content-Type-Options nosniff
X-Frame-Options DENY

style-src の inline style allowance は、安全に検証された Theme の値を CSS custom properties として設定するためのものであり、任意 CSS や HTML/JavaScript の拡張を許可するものではない。