URLひとつで安全に — トークン認証と時間軸の設計

パスワードなしでどう守るか。推測不能なトークンと有効期間、段階的な情報開示

トークン認証マジックリンクHMAC有効期限アクセス制御
読了時間: 9分

はじめに

「URLを知っていれば誰でも見られる」というと、無防備に聞こえるかもしれません。しかし設計次第で、パスワード方式に劣らない安全性を確保できます。

この記事では、宿泊者専用ページをパスワードなしで守るために行った工夫を紹介します。暗証番号という機密情報を扱うページなので、慎重に設計しました。

推測できないURLを作る

ランダムな文字列を使う

URLに含める文字列(トークン)は、予測できないランダムな値を使います。予約番号や連番のような、規則性のある値は絶対に使いません。

十分な長さのランダム値を使えば、総当たりで当てることは現実的に不可能です。「たまたま他人のページが開けてしまった」ということも起こりません。

データベースには「変換した値」を保存する

ここが重要なポイントです。発行したトークンそのものは、データベースに保存しません。保存するのは、そのトークンを一方向の変換にかけた結果です。

トークンの保存方法
BEFORE
そのまま保存

データベースが漏洩したら、全宿泊者のページに誰でもアクセスできてしまう

AFTER
変換した値を保存

漏洩しても、変換後の値からURLは復元できない

アクセスがあったときは、URLに含まれるトークンを同じ方法で変換し、保存されている値と一致するかを確認します。パスワードの保存方法と同じ考え方です。

変換に使う秘密の鍵は、管理画面のログイン用とは別のものを使っています。片方が漏れても、もう片方には影響しません。

管理画面での識別用に末尾4桁だけ残す

運営者が「どのURLを発行したか」を管理画面で確認できるよう、トークンの末尾4文字だけを別途保存しています。これだけでは復元できませんが、複数発行したときの見分けには十分です。

発行された完全なURLは、発行直後に一度だけ画面に表示されます。閉じたら二度と表示できません。必要なら再発行します。

有効期間を持たせる

開始と終了を明確に決める

トークンには利用可能な期間を設定しています。開始は発行した瞬間、終了はチェックアウト日の終わりまでです。

宿泊が終わったら自動的に使えなくなるので、いつまでも暗証番号が見られる状態にはなりません。

日程変更のときに動かす値・動かさない値

運営者が予約の日程を変更したとき、有効期間も連動して調整する必要があります。ここで気をつけたのが、終了日は動かすが、開始日は絶対に動かさないというルールです。

失効させる手段を用意する

URLが第三者に渡ってしまった疑いがあるときのために、失効させる機能を用意しています。管理画面から古いURLを無効にし、新しいURLを発行できます。

一方で、予約管理システム側でキャンセルされたときは、あえて失効させません。予約の状態を「キャンセル済み」にするだけです。

理由は、キャンセルが取り消される可能性があるからです。失効させてしまうと、キャンセルを取り消しても同じURLは使えず、お客様に新しいURLを配り直す必要が出ます。予約状態のほうを先に確認する作りなので、キャンセル中はお客様から見て「使えない」状態になり、復活すればまた使えます。

すべての入り口で確認する

画面もAPIも同じ検査を通す

ポータルには、トップページのほかに入室方法のページ、ハウスルールのページ、AIアシスタントの通信口、資料の配信口があります。

これらすべてで、独立してトークンの検証と期間の確認を行っています。「トップページを通過したのだから、その先は自由」という作りにはしていません。URLを直接入力されても、それぞれの入り口で止まります。

アクセスのたびに行う確認
トークンの形式を確認

長さが異常な値は、データベースを見る前に拒否

変換して照合

保存されている値と一致するトークンを探す

予約の状態を確認

キャンセル済みなら、この時点で拒否

失効と期間を確認

失効済み、期間前、期間後をそれぞれ判定

失敗はすべて同じ見た目にする

無効なトークン、失効したトークン、存在しないトークン — これらは区別せず、すべて同じ「見つかりません」という表示を返します。

「このURLは存在するが失効している」と分かってしまうと、そのURLが有効だった時期があることを教えることになります。細かいようですが、情報を与えない姿勢が大切です。

期間前と期間後だけは、お客様への案内として区別して表示します。ただしその画面には、予約内容も暗証番号も一切表示しません。連絡先だけです。

検索エンジンとキャッシュから隔離する

ポータルのページには、検索エンジンに登録しない指定と、キャッシュを残さない指定を必ず付けています。

共用のパソコンで開いたあと、ブラウザの戻るボタンで内容が見えてしまう事態を防ぐためです。

本人確認が必要な操作

キャンセルのように取り返しのつかない操作では、URLを知っているだけでは実行させません。予約時のメールアドレスの入力を求め、一致した場合だけ手続きを進めます。

この照合には、入力内容の違いによって処理時間が変わらない比較方法を使っています。応答時間の差から情報が推測されることを防ぐためです。

また、キャンセル手続きの試行回数にも制限を設けています。この制限を管理する際も、トークンそのものではなく変換後の値を使い、生のトークンがサーバーのメモリ上に残らないようにしました。

まとめ

パスワードなしのアクセス制御で押さえたのは、次の3点です。

  1. 保存するのは変換後の値 — データベースが漏れてもURLは復元できない
  2. 開始日は動かさない — 一度使えるようになったURLを、使えない状態に戻さない
  3. すべての入り口で検証する — 画面を通過したことを、その先の権限と見なさない