既存システムへ新しい認証処理や外部サービス連携を追加するとき、すべての処理を同じ言語・同じアプリケーションへ寄せる必要はありません。
たとえば、外部認証だけをPHPで受け取り、ログイン済みセッションの発行は既存のCGIや別アプリケーションへ任せたい場合があります。このような境界を安全につなぐ方法として、短命のワンタイムトークンを使うと構成を比較的単純にできます。
1、認証結果そのものをURLへ載せない最初に避けたいのは、ユーザーIDやメールアドレス、認証結果をそのままクエリ文字列へ載せて別アプリへ渡す方法です。
https://example.invalid/login.cgi?user=123&authenticated=1このような値は利用者側から書き換えられるため、信頼できる認証情報として扱えません。
代わりに、認証が完了したアプリケーション側で十分に長いランダム値を発行し、その値だけを次のアプリケーションへ渡します。
2、DBには生トークンではなくハッシュを保存するトークンは暗号学的に十分な乱数から作成します。たとえば256bit程度のランダム値を生成し、ブラウザへ渡す値をtoken、DBへ保存する値をSHA-256(token)とします。
raw token
↓ SHA-256
token_hash + user_id + expires_atこれならDBの内容だけを見ても、そのままログインに使えるトークンは分かりません。
3、有効期限を短くするワンタイムトークンはセッションそのものではなく、アプリケーション間の受け渡しにだけ使います。したがって有効期限を長くする必要はありません。
数十秒から数分程度にし、期限切れのものは無効として扱います。使われなかったトークンは定期処理で削除しても構いません。
4、消費時はトランザクションで一度だけ使う受け取り側では、ブラウザから届いたトークンを同じ方法でハッシュ化し、DBのレコードを検索します。このとき、取得と削除を別々の処理にせず、同じトランザクション内で行うのが安全です。
BEGIN;
SELECT user_id
FROM login_ticket
WHERE token_hash = :hash
AND expires_at > CURRENT_TIMESTAMP
FOR UPDATE;
DELETE FROM login_ticket
WHERE token_hash = :hash;
COMMIT;この形なら、同じトークンを二重に利用しようとしても、一方が消費した後は使えません。
5、既存のセッション処理を再利用できるこの方式の利点は、新しく追加した認証処理が既存のセッション形式を知る必要がないことです。
新しい認証処理
↓
短命トークン発行
↓
既存ログイン処理
↓
従来どおりセッション発行既存側のログイン後処理を大きく作り直さず、認証部分だけを別実装へ差し替えられます。
6、ログへ生トークンを残さない実装時にはアクセスログやエラーログにも注意します。生のワンタイムトークンをログへ出すと、有効期限内なら再利用される可能性があります。
トラブル調査用には、トークンそのものではなく、内部の連番やユーザーID、処理結果だけを記録するほうが安全です。
7、異なる技術スタックの橋渡しとして使いやすい新旧システムが混在していると、すべてを一度に置き換えるのは現実的ではありません。短命ワンタイムトークンを境界に置くと、認証処理と既存セッション処理を疎結合にでき、段階的な改修がしやすくなります。
特定の外部認証に限らず、別ドメイン間の一時的な引き継ぎや、管理画面から別アプリへ安全に遷移させる場合にも応用できます。


