本文へ移動
runlot

ログイン画面を自作する

アプリのログイン画面を自分で描き、Runlot ID へ直接つなぎ、エラーを扱い、Runlot の画面をカスタマイズし、ローカルで試します。

アプリは Runlot 画面の前後のログイン体験を自分で作り、Runlot 画面の見た目も決められます。Runlot ID のパスワードは引き続き Runlot のページでのみ入力します。自作の画面は Runlot へリンクするか、プロジェクト専用の方法を使います。

Runlot ID へ直接つなぐ

/__runlot/auth/sign-in/runlot は省略可能なパラメーターを 4 つ受け取ります。指定しなければ従来どおりに動作します。

パラメーター効果
idpgoogle か github なら選択画面を飛ばして直接そのプロバイダーへ進みます。password はメール入力画面を、passkey はパスキーの画面を開きます。オフの方法は無視され、選択画面が表示されます。
login_hintRunlot 画面のメール欄をあらかじめ埋めます。自動では送信せず、アカウントに関する情報はアプリに返りません。
error_nextエラー時に next の代わりに戻る先です。next と同じ規則の同一オリジンのパスです。
promptnone なら、すでに Runlot にログインしていてこのアプリを使ったことがある人を画面なしでログインさせます。そうでなければ login_required で戻ります。
import { signInUrl } from "@runlot/auth/client";

signInUrl("runlot", { idp: "google", next: "/chat" });
// /__runlot/auth/sign-in/runlot?next=%2Fchat&idp=google
signInUrl("runlot", { idp: "password", loginHint: "[email protected]", errorNext: "/login" });

idp、loginHint、errorNext、prompt は "runlot" にのみ使え、ほかのプロバイダーに渡すと型エラーになります。どの方法が有効かは config() でわかります。runlot プロバイダーの項目に ["password", "google", "github"] のような methods が入っています。

エラー: auth_error

ログインが成功しなかった場合、error_next(なければ next)へ 2 つのクエリパラメーター付きで戻ります。

/login?auth_error=cancelled&auth_request=6f1c…
コード意味
cancelled戻るボタンを押したか、プロバイダーの画面を閉じました
signup_closed新規登録がオフで、既存のアカウントがありません
email_unverifiedメール確認が必須ですが、プロバイダーのメールアドレスが未確認です
provider_unavailableログインの途中でその方法がオフになりました
rate_limited試行回数が多すぎます
expiredログインリクエストが古くなりました
consent_denied同意されませんでした
login_requiredprompt=none で、使えるセッションがありません
account_existsメールアドレスでの連携がオフで、そのメールアドレスは別のアカウントのものです
server_errorその他。サポートは auth_request で調べます

コードにはメールアドレス、プロバイダーのメッセージ、スタックは含まれません。@runlot/auth は閉じたコード一覧と、韓国語・英語・日本語の既定の文言を提供します。

import { authErrorMessage, parseAuthError, stripAuthError } from "@runlot/auth/client";

const err = parseAuthError(location.href); // { code, request } | null
if (err) {
  show(authErrorMessage(err.code, "ja"));
  stripAuthError(); // 再読み込みで再表示されないよう auth_error/auth_request を消します
}

不明なコードは server_error として扱います。

<runlot-sign-in>

依存関係のない Web コンポーネントです。有効な Runlot のログイン方法をリンクとして描き、auth_error の文言を表示してからアドレスバーから消します。gzip で約 4 KiB です。

<script type="module">import "@runlot/auth/ui";</script>
<runlot-sign-in next="/chat" lang="ja" methods="google github password" guest error-next="/login"></runlot-sign-in>
属性
next、error-next、login-hintすべてのリンクに渡します
langko(既定)、en、ja
methods描くボタンをスペース区切りで指定します。runlot は通常の選択画面のボタンです。既定はプロジェクトが提供する方法で、プロジェクトがオフにした方法は描きません
guestrunlot-guest イベントを送るゲストボタンを追加します

すべての要素に part があり、外からスタイルを当てられます。runlot-sign-in::part(button-google)、::part(error)、::part(root)。色、丸み、フォントは CSS カスタムプロパティで変えます。--runlot-brand、--runlot-brand-text、--runlot-bg、--runlot-border、--runlot-radius、--runlot-font、--runlot-gap、--runlot-error。header と footer のスロットに自作のマークアップを入れられます。

React:

import { RunlotSignIn } from "@runlot/auth/react";

<RunlotSignIn next="/chat" lang="ja" methods={["google", "github"]} guest onGuest={() => enterAsGuest()} />

プロジェクト専用の方法の JSON API

入力フォームをアプリのページ内に置きたい場合は、プロジェクトのパスワード・メールリンクの方法を JSON で使います。これらのアカウントはこのプロジェクトだけのもので、Runlot ID のアカウントではありません。

import { signInWithPassword, signUp, requestReset, confirmReset, sendMagicLink } from "@runlot/auth/client";

const r = await signInWithPassword({ email, password });
if (r.ok) location.href = "/";
else show(authErrorMessage(r.error, "ja")); // invalid_credentials, rate_limited, …
ルート本文
POST /__runlot/auth/api/password/sign-in{email, password} → 200 {user} とセッション Cookie
POST /__runlot/auth/api/password/sign-up{email, password, name?}
POST /__runlot/auth/api/password/reset{email} → 常に 202
POST /__runlot/auth/api/password/reset/confirm{token, password}
POST /__runlot/auth/api/magic-link{email, next} → 常に 202

リクエストは Content-Type: application/json である必要があり(そうでなければ 415)、アプリのオリジンから送る必要があります(そうでなければ 403 csrf)。エラーコードは上記に invalid_credentials、weak_password、check_email が加わります。429 には Retry-After が付きます。

Runlot 画面のカスタマイズ

Runlot の選択画面に、アプリの文言、レイアウト、色を反映できます。すべて省略可能で、既定値は現在の画面と同じです。カスタマイズはサーバーが検証するデータで、HTML や CSS を直接入れることはできません。

runlot auth set brand-copy ja.title "税法の原文にもとづいて答えます"
runlot auth set brand-layout split            # centered | split | minimal
runlot auth set brand-theme dark              # light | dark | system
runlot auth set brand-button pill             # rounded | pill | square
runlot auth set brand-background color "#0b1020"   # none | color | gradient | image
runlot auth set brand-logo-dark https://example.com/logo-dark.svg
runlot auth set brand-link privacy https://example.com/privacy
runlot auth set brand-lang ja                 # ko | en | ja | auto
runlot auth set brand-methods google,github   # all = 利用できる方法すべて

文言は言語ごとに、タイトル 80 文字、サブタイトルとフッター 200 文字までです。リンクと画像は https の URL である必要があります。

全体をファイルで管理するには:

runlot auth brand get > branding.json
runlot auth brand apply branding.json

不正なファイルは 400 invalid_branding と問題のある項目のパスで拒否されます。ダッシュボードの Sign-in & access タブにも同じフォームとライブプレビューがあります。

プレビュー

runlot auth preview                       # カスタマイズした Runlot 画面を開きます
runlot auth preview --lang en --theme dark --state error:cancelled

--state は password、passkey、error:<code> のいずれかです。実際にログインしなくても確認できます。

ローカル開発: runlot dev --auth=chooser

runlot dev --auth=chooser

こうすると /__runlot/auth/sign-in/runlot が、カスタマイズどおりの見た目のローカル選択画面を返します。テスト用アカウントを選ぶとログインした状態で戻り、エラーボタンを押すとその auth_error コード付きで戻ります。idp、login_hint、error_next、prompt は本番と同じように動作します。カスタマイズ情報は API から一度だけ読み、.runlot/auth-branding.json に保存します。このファイルを消すと読み直します。オフラインのときは既定の見た目を使います。このオプションがなければ、runlot dev は従来どおり固定の開発用ユーザーでログインします。

アカウントの連携とユーザーの統合

既定では、Runlot ログインの確認済みメールアドレスが別の方法で登録した既存ユーザーと一致すると、そのユーザーに連携されます。分けておくには:

runlot auth set link-by-email off

すると、このようなログインは auth_error=account_exists で戻ります。

あとから 2 人のユーザーを統合するには(例: 自作の Google アプリから Runlot に切り替えたあと):

runlot auth users merge <from-user-id> <into-user-id>

<from> のすべてのログイン手段を <into> に移し、<from> をログアウトさせてから削除します。プロジェクトのデータベースで 1 つのトランザクションとして実行します。アプリのテーブルで <from> を参照している行はご自身で更新してください。ワーカーからは env.auth.users.merge(from, into) で同じことができます。

ダッシュボードのログインタブには直近 30 日のログインファネル(日ごとの開始、成功、エラーコード)も表示され、runlot auth funnel --days 30 は合計を出力します。個人単位のデータは保存しません。

このページの目次