今回は、JWTを利用した会員登録、ログインやユーザのアクセス権限制御の実装イメージを記します。
私は、これまでにSessionを利用したステートフルなアクセス権限制御を行なったことがありましたが、ステートレスなアクセス権限制御の実装は初めてでした。学習のためにも記しますので内容に誤り等ありましたら、ご指摘いただけると助かります。
JWT(Json Web Token)とは
Web上で安全かつコンパクトな方法で情報をやりとりするための標準規格(RFC 7519)のことです。ヘッダー、ペイロード、署名から成り立つトークンのことで、主に認証や情報伝達で使用されます。
JWTの情報
JWTは、
「eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1…」のようなトークン(文字列)です。
これをデコードすることで、
ヘッダー、ペイロード、署名の情報を見ることができます。
{ "iss":"application name", "iat":1702725472, "nbf":1702725472, "exp":1702727272, "user_id":"3833435494" }
上記のようなパラメータをクレームと呼び、他の予約済みクレーム及び用法は、
RFC 7519 JSON Web Token (JWT) に記載されています。
勘違い・難しく考えていたこと
概念は知っていても、どう実装すべきかイメージが湧きませんでした。
- JWTに秘匿情報は入れない。
JWTには、プライベートクレーム(ユーザの任意で設定できるクレーム)を加えることができ、それらによって、アクセス制限処理を組み込んだりできます。
ただ、追加するクレームに使う情報は、秘匿情報を含まず、個人を識別できるIDなどを使用するのが一般的です。例えば、UIDなどを使用します。
JWTでアクセス制御を実装すると、予約クレームのiatからトークンの発行日、expからトークンの有効期限を検証できたり、改ざん検知などが可能です。
また、秘匿情報を入れないのは、Base64デコードで中身が見れるからです。
公開鍵方式、共通鍵方式などで完全性が担保されているため、それらのクレームから何かしらの認証を行うのが一般的です。
予備知識
- 発行するJWTについて
発行するJWTは、アクセストークンとリフレッシュトークンです。
それぞれの詳細は後述しますが、アクセストークンは、アクセス制限のあるリソースにアクセスする際に使用されます。リフレッシュトークンは、アクセストークンを再発行するために使用します。
- アクセストークンについて
生成したJWTのうち、一つはアクセストークンとして使用されます。
これは、ユーザがアクセス制限のあるリソースにアクセスする際に使用されます。
有効期限が短く、リクエストの一回限りのトークンであることが特徴です。
アクセストークンは、生成後にCookieに保持されます。HttpOnly属性を指定すれば、JSからアクセスを制限できますが、機密性が担保されるわけではありません。万一、他のユーザのアクセストークンが漏れてしまった場合、そのユーザのリソースにアクセスされる恐れがあります。
そのため、アクセストークンは有効期限を短くし、アクセス一回限りのトークンする必要があります。
本記事では、有効期限を30分としました。
有効期限はサービスによってまちまちですが、1時間以内に設定するのが一般的でしょう。
- リフレッシュトークンについて
前述したアクセストークンの有効期限が切れた場合に、ログイン認証を必要とせずアクセストークンを再発行するために使用されるトークンです。
このトークンは、クライアントへは送信せず、サーバサイドでのみ使用します。
アクセストークンの有効期限が切れた場合、そのアクセストークンからリフレッシュトークンを照合し、アクセストークンを再発行します。
(さらにセキュアにするには、アクセストークン再発行時に、リフレッシュトークンも再発行)
リフレッシュトークンは、有効期限を長めにセットします。
本記事では、有効期限を30日間としました。
リフレッシュトークンを持たせることで、アクセストークンの流出が発覚した場合、
サービス管理者または正規なユーザがリフレッシュトークンの書き換えを行い流出したアクセストークンを無効化できます。
もし、アクセストークンのみの運用の場合、サービス側で手の打ちようがなくなってしまうため、リフレッシュトークンの実装は必須です。
実装イメージ
JWTの生成
①ログイン認証
まず、ユーザのリクエスト(ID/Pass)がログイン認証等で正規のものと証明します。
②アクセストークン(JWTの発行)
{ "iss":"application name", "iat":1702725472, "nbf":1702725472, "exp":1702727272, "user_id":"3833435494" }
issには、サービス名や発行者を指定します。
iatとnbfは、発行時のタイムスタンプを指定します。
(iatは、発行日時を指定しnbfは有効開始日時を指定するためです。)
expは有効期限を指定します。
プライベートクレームで、認証されたuser_idを追加します。
有効期限は、発行日時から30分後としました。
{ "iss":"application name", "iat":1702725472, "nbf":1702725472, "exp":1705317472, "user_id":"3833435494" }
リフレッシュトークンも併せて発行します。
クレームは、アクセストークンと同じですが有効期限のみ発行日から30日間としました。
③DBにアクセストークン/リフレッシュトークンを保存
賛否あると思います。(不適であればご指摘ください。)
UserId,AccessToken,RefreshTokenをトークン用のテーブルに保存します。
④アクセストークンをクライアントへ返す
アクセストークンをクライアントへ返却します。
Cookieにセットします。
アクセストークンの認証
①制限のあるリクエスト
自分のリソースにアクセスするときや、ユーザの証明が必要な時に、CookieのAccessTokenを送信します。
②アクセストークンの検証
アクセストークンを署名した秘密鍵とツーペアな公開鍵で、アクセストークンを検証します。
ここでは、改ざんや有効期限などのクレーム情報を検証します。
もし、改ざん検知した場合は、ユーザに再ログインを求めます。
③アクセストークンの再発行
アクセストークンが正規なものと検証されたが、既に有効期限が切れている場合は、リフレッシュトークンを用いてアクセストークンを再生成します。
まず、DBからアクセストークンに紐づくリフレッシュトークンを取得します。
そして、アクセストークンと同じく検証を行います。
もし、有効期限切れなどを検知した場合は、ユーザに再ログインを求めます。
まだ有効の場合は、リフレッシュトークンをデコードし、プライベートクレームからIDを取得します。
プライベートクレームにIDを加えアクセストークンを生成します。
生成後は、先ほどのリフレッシュトークンに紐づく形でDBに保存。何かしらのレスポンスと併せてアクセストークンをCookieにセットします。
コメント