genesis-auth について
genesis-authは、既存のgenesisプロジェクトに認証コードを追加するスキルです。プロジェクトの構成と要件に合わせ、初期管理者ログインからMFAや監査ログまで3段階のセキュリティを実装します。
主な機能
- 01basic、standard、strictの各ティア
- 02Cookieとサーバー側セッション
- 03GoogleとAppleのSSOに対応
- 04パスワードリセットとロール確認
- 05MFA、レート制限、監査ログ
活用シーン
- 01genesisサーバーへの認証機能の追加
- 02アプリの機密性に応じた認証方式の選択
- 03ブラウザーやモバイル/API利用者への対応
- 04既存のデータベース連携プロジェクトでの認証拡張
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lwlee2608/ genesis --skill genesis-authインストール後、Claude Code で/genesis-authを入力して読み込まれたか確認します。
詳細
genesis-auth
genesis プロジェクトに認証機能を追加します。ユーザーがティアを選び、要件を伝えると、このスキルが認証方式を導き出します。JWT と Cookie のどちらにするかなど、実装方法についてユーザーに質問することはありません。その後、対象プロジェクトにコードを直接書き込みます。
このスキルはガイドラインであり、コードのコピー作業ではありません。複製するための参照実装はありません。対象プロジェクトの既存コードを読み、それに合った認証機能を実装してください。
前提条件: サーバーには genesis の DB レイヤー(goose マイグレーション、sqlc、users テーブルを含む internal/db)が必要です。genesis CLI はこれを生成しません。存在しない場合は作業を止め、まず /genesis sqlc で追加するようユーザーに伝えてください。
ティア
| ティア | 用途 | 含まれる機能 |
|---|---|---|
basic | 内部ツール | 環境変数からシードされる単一の管理者、パスワードログイン、Postgres の Cookie セッション。サインアップとメールはありません。 |
standard | 一般的なアプリ | メール認証付きのメールアドレスとパスワードによるユーザー登録、任意の Google/Apple SSO、Postgres のセッション、パスワードリセット、管理者/ユーザーロール。 |
strict | 機密データ | standard に TOTP MFA、レート制限と一時ロックアウト、権限変更時のセッションローテーション、監査ログを追加します。これ以上は独自実装のコードを増やすのではなく、外部 IdP(Keycloak、Auth0、WorkOS)を推奨してください。 |
質問の流れ
-
まず引数を確認します。
basic/standard/strictはティアを、google/appleは SSO を指定します。不足している項目だけを質問してください。 -
残りの質問を 1 回の AskUserQuestion 呼び出しで尋ねます。 推奨する選択肢を最初に置き、「(Recommended)」と付けてください。
- ティア:
basic|standard (Recommended)|strict - クライアント:
browser only (Recommended)|also mobile/API consumers - SSO プロバイダー(複数選択可):
google|apple— 選択なしの場合は SSO を使いません。
basicはブラウザー専用で、SSO はありません。引数ですでにbasicが指定されている場合は、クライアントと SSO の質問を省略してください。同じ呼び出しでユーザーがbasicを選んだ場合は、それらの回答を無視してください。引数でbasicとプロバイダーが同時に指定されている場合は、確認時に矛盾を伝えてください。 - ティア:
-
認証方式は自分で導き出し、質問しないでください。
- ブラウザーのみ → Cookie(HttpOnly、Secure、SameSite=Lax)と、Postgres に保存するサーバーサイドのセッショントークンを使用します。ウェブアプリは Vite プロキシ/nginx 経由で同一オリジンから API にアクセスするため、CORS は追加しません。Lax でもクロスサイトの GET では Cookie が送信されます。状態を変更する処理を GET に置かないでください。また、Cookie 認証を使う更新リクエストは、送信元と異なる
Originの場合に拒否してください。 - モバイル/API コンシューマー → ウェブには同じ Cookie フローを使用し、モバイル向けには同じセッションテーブルから不透明なベアラートークンを発行し、マシンコンシューマーには API キーを使用します。JWT は使用しないでください。ログアウト、リセット、ロール変更時に取り消せないためです。
- ブラウザーのみ → Cookie(HttpOnly、Secure、SameSite=Lax)と、Postgres に保存するサーバーサイドのセッショントークンを使用します。ウェブアプリは Vite プロキシ/nginx 経由で同一オリジンから API にアクセスするため、CORS は追加しません。Lax でもクロスサイトの GET では Cookie が送信されます。状態を変更する処理を GET に置かないでください。また、Cookie 認証を使う更新リクエストは、送信元と異なる
-
追加の選択肢を提示せず、導き出した計画を文章で確認します。 例:「basic ティア:シードされた管理者、Postgres の Cookie セッション、SSO なし、サーバーのみ(ウェブログイン UI なし)—この内容で進めますか?」ユーザーが認証方式を変更する(例:「やはり JWT にして」)またはウェブ UI を求める場合は、トレードオフを説明したうえで、その希望に沿ってください。
実装ルール
- 何かを書く前に、対象プロジェクトを読みます。 ルーター、設定構造体、マイグレーション番号、パッケージ構成によって、新しいコードの形が決まります。それらに合わせ、本文書の構成を押し付けないでください。
- 既存の users テーブルを拡張し、並行する別のテーブルを作らないでください。 genesis サーバーには、
usernameが一意で、password_hashとuser_roleenum を持つinternal/db/migrations/0001_create_users.sqlがすでに含まれています。新しい番号付きマイグレーションで列とテーブルを追加してください。standard以上では、一意のemailを小文字で保存し、メールアドレスでログインさせ、サインアップ時にusernameをそのメールアドレスに設定してください。データが入ったテーブルでは、ユーザー名がメールアドレスだと決めつけないでください。NOT NULLを適用する前にデータをバックフィルし、メールアドレスの欠落や大文字・小文字の衝突は推測で処理せず、ユーザーに知らせてください。 - genesis サーバーの既存の構成に従ってください。
internal/auth/— パスワードのハッシュ化、セッション/トークンの発行と検証、プロバイダークライアント。ここでは gin の型を使いません。internal/api/http/handler/auth.go— ログイン、ログアウト、サインアップ、リセットのエンドポイント。internal/api/http/dto/auth.go— リクエスト/レスポンスの構造体。internal/api/http/middleware/auth.go— セッション/Bearer の検証、ロールの確認。internal/db/migrations/NNNN_*.sql— goose の-- +goose Up/Downブロック。両方向を用意します。internal/db/queries/*.sql— sqlc の名前付きクエリ。編集後にmake generateを実行します(make depで sqlc をインストールできます)。
- すべてを接続してください。
SetupRouteでハンドラーとミドルウェアを登録し、新しい依存関係があればServices構造体に追加して、main.goで構築します。コンパイルできてもマウントされていない認証コードは、完成したとはいえません。 - 既存の仕組みを通じて設定します。
internal/authでauth.Configを定義し、db.Configと同様にcmd/<app>/config.goの構造体に組み込みます。機密情報でないデフォルト値はapplication.ymlに記述し、すべてのキーを.を_に置き換えて大文字化した形式で.env.exampleに記載します(auth.sessionTtl→AUTH_SESSIONTTL)。シード用パスワード、クライアントシークレット、SMTP パスワード、キーなどの秘密情報はapplication.ymlに記載せず、mask:"true"タグを付けます。ローダーはデバッグ時(デフォルトのログレベル)に設定全体を出力します。ローダーのフィールドバインディングに従ってください(adderスキルを参照)。os.Getenvを直接読み取ってはいけません。 - まず標準ライブラリと既存の依存関係を使ってください。 パスワードには
golang.org/x/crypto/bcrypt、トークンにはcrypto/randを使います。該当する層で本当に必要な場合(OIDC、TOTP)に限って依存関係を追加し、go mod tidyを実行してください。 - 秘密情報をログに記録したり、応答に含めたりしてはいけません。 パスワード、ハッシュ、セッショントークン、リセットトークンをログやエラーレスポンスに含めないでください。セッション、リセット、確認、API の各トークンは SHA-256 のハッシュ(
crypto/randで生成した 32 バイト)だけを保存します。トークンハッシュは検索時にインデックスを使える必要があるため、bcrypt はパスワードのみに使用します。アカウントの有無を明かしてはいけません。不明なユーザーと誤ったパスワードには同じエラーを返し、タイミングが同程度になるようダミーハッシュと比較します。リセットリクエストにはすべて同じ応答を返してください。 - リセット処理はアトミックにしてください。 リセットトークンの検証と消費、パスワードの更新、セッションの失効を、1 つのトランザクションで行います。同時リクエストが両方成功しないよう、トークンをロックするか、条件付きで消費してください。
- すべてのタイムスタンプ列に
TIMESTAMPTZを使用してください。 genesis のusersテーブルではすでに使用されています(created_at、updated_at)。また、sqlc はこれをpgtype.Timestamptzにマッピングします。セッション/リセットの有効期限やライフサイクルを示す列にも同様に使用してください。オフセットが失われ、UTC 以外のデータベースで有効期限の確認に問題が生じるため、通常のTIMESTAMPを混在させてはいけません。 - 有効期限の経過は、クリーンアップを意味しません。 適切なインデックスを用意し、有効期限切れのセッションと期限切れ/使用済みのリセットレコードを定期的かつ上限付きで削除する処理を組み込んでください。
- bcrypt のバイト数上限を検証してください。 サインアップ/リセット時に 72 バイトを超えるパスワードをクライアント入力エラーとして拒否します。文字数によるバリデーターでは不十分です。
internal/authのMailerインターフェースを通じてメールを送信し、SMTP 実装を用意してください。 SMTP が設定されていない場合、リセットと確認のリクエストは 503 を返し、トークンの行を作成してはいけません。配信の代わりにリンクをログへ出力してはいけません。起動時の動作をログレベルに依存させてはいけません。- SSO。
golang.org/x/oauth2とgithub.com/coreos/go-oidc/v3を使い、state、PKCE、nonceを確認して、ID トークンを検証してください。ID は独立したテーブルで(provider, subject)をキーにし、メールアドレスをキーにしてはいけません。SSO のみのユーザーではpassword_hashを NULL 許容にしてください。既存ユーザーへの紐付けは、双方のメールアドレスが確認済みの場合に限ります。そうしなければ、被害者のメールアドレスを先に登録した攻撃者がアクセス権を維持してしまいます。Apple はコールバックをクロスサイトで送信する(form_post)ため、state Cookie にはSameSite=None; Secureが必要です。Apple のクライアントシークレットは最長 6 か月有効な ES256 JWT なので、.p8キーから実行時に生成します。ユーザー名は初回サインイン時にのみ送信されます。 - すべての層で、設定から最初の管理者をシードしてください。
Adminユーザーが存在しない場合にのみ作成し、既存のユーザーを上書きしてはいけません。存在せず、シード用パスワードも空の場合は、明確なエラーを出して起動に失敗させます。サインアップでは常にUserを作成し、リクエストからroleを受け取ってはいけません。 - 追加の厳格な要件。 レート制限とロックアウトの状態は Postgres に保存してください(インメモリではレプリカ環境で機能しません)。gin の信頼プロキシには実際のプロキシチェーンを設定してください。デフォルトでは gin はすべてのプロキシを信頼するため、
ClientIP()は偽装可能なX-Forwarded-Forを読み取ります。ロックアウトには期限とバックオフを設けてください。永続的なロックアウトでは、誰でも管理者を締め出せます。TOTP の秘密情報は保存時に暗号化し、コードの再利用を拒否し、リカバリーコードはハッシュ化してください。
検証手順
- 対象サーバーで
make buildが成功すること。 make generateがクリーンに完了し、生成されたコードがツリー内にあること。- コードがクエリする新しいテーブルまたはカラムごとに、マイグレーションファイル(up と down の両方)が存在すること。
- 新しい設定キーがすべて
.env.exampleに記載されており、シークレットではないキーはapplication.ymlにも記載され、シークレットフィールドにはmask:"true"が設定されていること。 - Cookie セッションの場合、Cookie に
HttpOnly、Secure、SameSiteが設定されていること。 - セッショントークンがハッシュ化されて保存され、有効期限カラムがあり、ログアウト時に行が削除されること。
- 実際に実行すること。ビルドの成功や
[no test files]は検証ではありません。プロジェクトの docker-compose Postgres を起動し、LOG_LEVEL=infoでサーバーを実行して、ログイン、ログアウト、保護されたルート、ロールチェックの各フローを curl で確認します。新規データベースと既存ユーザーがいるデータベースの両方でマイグレーションし(既存ユーザーが引き続きログインできること)、ALTER DATABASE ... SET timezone = 'Asia/Singapore'の後に有効期限を再確認します。 standard以上の場合:同じトークンを使った同時リセットが 2 件発生したとき、成功するのはちょうど 1 件であること。リセットに失敗した場合はロールバックされること。éが 40 文字(80 バイト)の場合は 4xx で拒否されること。期限切れの行がクリーンアップされること。
よくある間違いに注意
- すでに回答済みの引数を再度尋ねること。
/genesis-auth basicで尋ねるのは、最後の確認だけにしてください。 Downブロックのないマイグレーションを作成すること、または新しいマイグレーションを追加せず、適用済みのマイグレーションを編集すること。internal/db/sqlc/を手動編集すること。 ここは生成されるため、.sqlクエリを変更して再生成してください。