はじめに
最近 Next.js を使ってフロントエンドとバックエンドのプロジェクトをいくつか作成し、その中で better-auth をユーザー認証に使用しました。
しかし実際に使ってみると、AI が直接関連コードを書くとパスワードを平文で送信してしまいがちです。本番環境では通常 HTTPS が使われ、トランスポート層では TLS 暗号化が行われていますが、だからといってパスワードを平文のままリクエストボディに含めても良いわけではありません。
HTTPS はブラウザから TLS 終端(nginx など)までの間の伝送セキュリティを保証するだけで、パスワードはリバースプロキシ、ゲートウェイ、ログ、データベースなどで依然として平文のまま現れます。つまり、これらのアクセス権限を取得できた者は誰でも全ユーザーのアカウントにログインできてしまいます。個人プロジェクトであればパスワード暗号化を無視しても構いませんが、製品としては到底受け入れられません。
そこでこのブログ記事を書きました。better-auth を使用する際にアプリケーション層でパスワードを RSA 非対称暗号化する方法を解説します。この内容を AI に与えれば他のプロジェクトでも同様の実装ができるかもしれませんが、MIT ライセンスに従ってクレジットを表示してください。詳細は文末をご覧ください。
全体の流れ
- ユーザーがブラウザでパスワードを入力します。
- フロントエンドが
NEXT_PUBLIC_RSA_PUBLIC_KEYを読み取ります。 - RSA-OAEP を使用してパスワードを暗号化します。
- 暗号化パッケージにはタイムスタンプ、ランダムソルト、リクエスト ID、HMAC 署名が含まれます。
- フロントエンドが暗号化された文字列を better-auth に渡します。
- サーバー側で better-auth の
password.hash/password.verify内で復号します。 - 最終的にデータベースには bcrypt ハッシュのみが保存されます。
この流れ全体で、平文のパスワードはユーザーがパスワードを入力するときだけ現れます。
クライアント側の暗号化
ここではパスワードだけを暗号化するのではなく、いくつかのフィールドをまとめて RSA 暗号文に入れています:
その内訳:
passwordはユーザーが入力した元のパスワードです。timestampはリクエストが期限切れかどうかを判断するために使います。saltは同じパスワードでも毎回異なる暗号文を生成するために使います。requestIdはリプレイ攻撃を防ぐために使います。
ログインページでは、better-auth に送信する前に rsaEncrypt を呼び出します。
登録時も同様です:
こうすることでパケットキャプチャで見える password フィールドは元のパスワードではなく、RSA 暗号化されたデータパケットになります。

サーバー側の復号
まず秘密鍵で復号します:
その後、一括して検証を行います:
ここでは主に以下のことを行っています:
- 暗号化パッケージの形式をチェックします。
- タイムスタンプが期限切れでないかチェックします。
requestIdが既に使用されていないかチェックします。- RSA 秘密鍵で復号します。
- 外側のタイムスタンプと内側のタイムスタンプが一致するかチェックします。
- 外側のリクエスト ID と内側のリクエスト ID が一致するかチェックします。
- HMAC 署名を検証します。
- パスワードの bcrypt ハッシュを返します。
requestId の重複排除には Redis を使用します:
つまり、最初のリクエストだけが書き込みに成功し、同じ requestId を後から再利用すると拒否されるため、リプレイ攻撃を防ぎます。
公開鍵と秘密鍵のペアを生成するコマンドは以下の通りです:
もちろん ssh-keygen を直接使うことも可能です。そして、秘密鍵の安全は必ず確保してください。
better-auth への組み込み
better-auth はデフォルトでメールアドレスとパスワードのログインを処理しますが、ここでは暗号化とハッシュ化を実現するためにパスワード処理ロジックをカスタマイズする必要があります。
ここで一つ注意点があります。better-auth の minPasswordLength は 1 に、maxPasswordLength は 4096 に設定されています。
理由は、better-auth に渡されるのは元のパスワードではなく、RSA 暗号化されたデータパケットだからです。デフォルトの長さ制限をそのまま使うと、better-auth の層で簡単にブロックされてしまいます。
パスワードの長さを制限したい場合は、クライアント側の rsaEncrypt 内で行う必要があります。
もちろん、better-auth はデフォルトでログインと登録の場面でのみパスワードの検証が必要です。パスワード変更やアカウント削除などの場面では、rsaEncrypt と rsaDecryptAndValidate を再利用するだけで済みます。
better-auth のその他のセキュリティ設定
パスワード暗号化に加えて、betterAuth の初期化時にいくつかのセキュリティ項目を設定できます:
その意味はおおよそ以下の通りです:
- CSRF チェックを無効にしない。
- 本番環境では Secure Cookie を使用する。
- Cookie に
httpOnlyを設定する。 - Cookie に
sameSite: "lax"を設定する。
そして、ログイン、登録、パスワード再設定のレート制限:
これにより、クレデンシャルスタッフィング、大量登録、大量メール送信などの問題を軽減できます。