私は主に確認メールのリンクに使用されるトークン生成と検証のための信頼性の高いシステムを開発しています(パスワードリクエストのリセット、メールフローの変更、アカウントのアクティブ化など)。
必須事項がいくつかあります。
トークンは、(データベース内で)システム内で(2つが同時に生成された場合でも)一意である必要があります。
トークンは1回限りの使用である必要があります
それから私はこのようなトークンを生成することを決めました:
token = sha256(user.id + time + uuid(v4) + secret)
このトークンは有効期限情報を保持する必要はありません。これは、これらの列が外部にあるデータベースに保存されるためです。
電子メールに送信されるこれらのタイプの使い捨てトークンの既知のエクスプロイトがいくつかあり、私が安全かどうかわからないので、これを尋ねています。
とにかく、すべての関連情報(トークン、有効期限、ユーザー)をデータベースに格納している場合、トークンについて確認する必要があるのは、トークンを推測することができないことだけです。
次の2つのうち少なくとも1つが成立する場合、トークンは推測できません。
実際、システムは必要以上に複雑です。トークンはデータベース内の情報を検索するためにのみ使用され、検証されないため、UUIDのみを使用できます-ハッシュ、シークレット、その他のデータは使用できません。確認する必要があるのは、UUIDが安全なランダムソースで生成されることだけです。
この情報を検証目的でのみ使用し、すでにデータベースに格納している場合は、ランダムなUUIDを生成し、必要なデータ(タイムスタンプ、ユーザー)とともにデータベースに格納するだけで十分です。これらの列をトークンにハッシュすることによる追加の利点はありません。このセットアップでは、データベースを介して同期し、1回限りの使用を確認する必要があります。
また、暗号化を使用して、Amazon S3などと同様の方法で、またはJWTで行われるのと同じ方法で、URLにデジタル署名することもできます。
通常のユーザーIDと有効期限のタイムスタンプをURLに挿入します。同様に保持する必要がある追加のデータでこれに署名します。パスワードのリセットはこのように実装されます
/reset-password?userid=X&expires=Y&signature=Z
署名を計算する
sign(userId=X,expires=Y,currentPasswordHash=..., privateKey)
ユーザーがこのリンクをクリックしたら、署名の整合性を確認し、有効期限が切れていないことを確認してください。暗号署名のため、ユーザーは署名を無効にしないとURLの2つのパラメーターを変更できません。 currentHasswordHashを署名に追加すると、ユーザーがパスワードの変更に成功すると、まだ有効期限が切れていなくても、署名は無効になります。
データベースに何も保存する必要がないという事実を平均化することもできます。サインアップ/オプトインでは、エンドユーザーが実際にオプトインリンクをクリックしない限り、エンドユーザーのメールアドレスを保存する必要はありません。
いくつかの追加の手順が必要ですが、ステートレスであるため、拡張性が向上します。また、DBをクリーンアップして古いトークンを消去する必要はありません。もちろん、すべてのコストで秘密鍵を保護する必要があります。そうしないと、誰かが有効なURLを生成する可能性があります。
安全だと思います。ユーザーID時間、uuid、およびシークレットを使用しているため。
誰かが自分のトークンを作成する場合、時間だけでそれは困難な要素になり、他の秘密が追加されると、それは煩わしいことになります。だからあなたの方法を知っていても、時間、uuid、秘密の両方を繰り返す必要があります。そして何のために?トークンがシステムにあり、有効期限が切れていない可能性はありますか?あるクライアントが大量のトークンを試行していることに気付いた場合は、それらをブロックするだけです。
シークレットとUUIDはあなたのシークレットであり、ユーザーはこれらを知りません。上記の知識があれば、(追加された時間により)毎回一意のトークンを取得できます。
しかし、なぜあなた自身の方法を作っているのですか?すでに多くのトークン生成ソリューションがあります。