このチャプターで達成すること
チャプター「サーバーサイドのログイン認証」では、RailsにJWTを使ったログイン認証機能を実装します。
具体的には、ログイン時にJWTをCookieに保存し、アクセス時にはそのJWTが有効であるかを検証する機能です。
まずはJWTとはなんぞや?というところから見ていきましょう。
今回達成すること
今回はJWTをゼロから理解します。
RailsにGem jwt
をインストールして、トークンの発行とトークンの検証を行います。
この記事を通して、JWTの使い方、メリット、注意点を理解していきましょう。
JWT認証とは?
JWT(ジョット)とは、「JSON Web Token(ジェイソン ウェブ トークン)」の略で、2つのパーティー間で情報を安全に送信するための方法です。
その実態は、JSONオブジェクトをエンコードした文字列で、この文字列をトークンと呼びます。
eyJhbGciOiJIUzI1NiJ9.
eyJleHAiOjE2MDA1OTY0MzEsInN1YiI6MSwibmFtZSI6InVzZXIwIn0.
lXcwASyLX5GEsMvPYDVhe0ovJj631fUiC0q2ojK-yK0
3つに分かれるトークン
JWTが発行したトークンは、ドットによって3つに分かれており、それぞれの情報を保持しています。
<ヘッダ>.<ペイロード>.<署名>
1. ヘッダ
最初の文字列をヘッダと呼びます。
ここにはトークンのタイプや、使用されている署名アルゴリズムの情報を持っています。
// エンコード => デコード
eyJhbGciOiJIUzI1NiJ9.
=> { "alg": "HS256", "typ": "JWT" }
2. ペイロード
2番目の文字列をペイロードと呼び、任意の情報を指定することができます。
基本的には、このペイロードをカスタマイズしてユーザー認証に必要な情報を埋め込みます。
// エンコード => デコード
eyJleHAiOjE2MDA1OTY0MzEsInN1YiI6MSwibmFtZSI6InVzZXIwIn0.
=> {"exp"=>1600596431, "sub"=>1, "name"=>"user0"}
また、このexp
やsub
などのそれぞれの値を「クレーム」と呼びます。
デフォルトで指定されている値を「予約クレーム」、使用者が任意に指定した値を「パブリッククレーム」と呼びます。
3. 署名
3番目の文字列には署名情報が入っています。
この署名は、トークンが変更されていないか確認するために使用されます。
// エンコード => デコード
lXcwASyLX5GEsMvPYDVhe0ovJj631fUiC0q2ojK-yK0
=> HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
参考 jwt.io
Railsにruby-jwtをインストールする
JWTを触って理解するために、Railsにruby-jwt
をインストールします。
ブランチ切っとく
と、その前にこれからの作業のブランチを切っておきます。
root $ cd api
api $ git checkout -b 20200910_jwt_authentication
api $ git branch
* 20200910_jwt_authentication
ブランチが切れたらルートディレクトリに戻ります。
api $ cd ..
ruby-jwtのインストール
それではRailsにruby-jwtをインストールしましょう。
jwt
を追記してください。
Gemfile
...
# JWT Doc: https://github.com/jwt/ruby-jwt
gem 'jwt', '~> 2.2'
group :development, :test do
...
apiサービスをビルドします。
root $ docker-compose build api
インストールできたか確認しておきましょう。
root $ docker-compose run --rm api bundle info jwt
* jwt (2.2.2)
...
Dockerダングリングイメージの削除
ビルドして新しいイメージができたので、ダングリングイメージを削除します。
ダングリング ... 最新ではない、
<none>
タグのイメージ
root $ docker image prune -f
Deleted Images:
deleted: sha256:b2f4bcbefb2d6b3bf2bdc8dfc592fcdf6941b085d0e3c9e5d0925b6340b7422d
...
-
-f
... 削除確認メッセージを出さない。
ダングリングイメージが削除されたか確認しておきましょう。
<none>
タグのイメージが表示されなければ成功です。
root $ docker images
JWTを発行しよう
Railsコンソールに入って、実際にトークンを発行してみましょう。
root $ docker-compose run --rm api rails c
ペイロードの作成
まずは、任意の情報を埋め込むペイロードを作成します。
> payload = { sub: 1 }
sub (Subject) クレーム
JWTの主語となる主体の識別子で、予約クレームの一つ。
。。。ちょっと何言ってるかわけわかめですね。
直訳すると「件名・主題」で、一般的にはオブジェクトを識別する一意性の値を指定します。
ユーザーテーブルで言うと、ユーザーIDのことです。
このクレームは任意で、{ user_id: 1 }
と言ったペイロードでも問題ありません。
ただし、他アプリケーションとの衝突を避けるために予約クレームを使用することが推奨されています。
予約クレーム一覧 予約クレーム名 - JWT
鍵の指定
トークン発行には、署名時に使用する鍵が必要です。
この鍵が漏れると、誰でもトークンの発行と検証ができるので、非公開鍵であるRailsのシークレットキーを使用します。
> secret_key = Rails.application.credentials.secret_key_base
JWTを発行する
準備ができたのでトークンを発行しましょう。
トークンの発行にはJWT.encode
メソッドを使います。
> token = JWT.encode(payload, secret_key)
> token
=> "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjF9.ZY3-9YTmrTz8H0t22DkyGREF_IZCgFkNoMjzHSGN_8U"
-
JWT.encode(payload, key, algorithm = 'HS256', header_fields = {})
-
algorithm
... 署名アルゴリズムの指定。デフォルトは
HS256
が指定されています。 -
header_fields
... ヘッダーフィールド。ヘッダー部分に埋め込む情報を、ハッシュで指定します。
-
JWTをデコードする
発行したトークンの中身を確認するには、JWT.decode
メソッドを使用します。
> JWT.decode(token, secret_key)
=> [{"sub"=>1}, {"alg"=>"HS256"}]
-
JWT.decode(jwt, key, verify = true, options = {})
-
verify
... バリデーションの有無。必ず、
true
を指定してください。ここを
false
にした場合、トークンが改ざんされていてもエラーを出さず、デコードできてしまいます。 -
options
... 追加オプションの指定。デフォルトのオプションは下記URLより確認できます。
-
JWTは何が良いのか?
ここからは「JWTって結局何が良いの?」と言う部分を説明します。
JWTの良いところ①
JWTの最大のメリットは、情報が改ざんできないことです。
トークンが正しいかを検証する際、署名アルゴリズム(計算式)と一致したトークンでなければエラーを出します。
例えば上記のトークンに"A"を足してみましょう。
> token += "A"
これをデコードした場合、エラーとなります。
> JWT.decode(token, secret_key)
=> JWT::VerificationError (Signature verification raised)
JWTの良いところ②
この仕組みを認証に使えば、ユーザーテーブルにトークンを一時的に保有するカラムを作成しなくて良くなります。
ユーザーテーブルが簡潔になりますね。
JWTの良いところ③
トークン発行時に電子署名を付与することができるので、署名をした鍵を持つものしかトークンを検証することができません。
鍵を持たないものが「トークンが正しい!」と判断することはできないのです。
新しいトークンを発行して、発行時と違う鍵でデコードしてみます。
> token = JWT.encode(payload, secret_key)
> key = "230sdjhviuhr8249492"
> JWT.decode(token, key)
=> JWT::VerificationError (Signature verification raised)
ご覧の通りエラーになります。
それでは鍵がない場合はどうでしょう。
> JWT.decode(token, nil)
=> JWT::DecodeError (No verification key available)
ありゃ、エラーですね。
このように、署名時に使用した鍵を使ってトークンをデコードしないとエラーになります。
つまり、鍵が漏洩しない限り、JWT検証装置が乗っ取られることはありません。
ちなみに、
- 署名時に付けた鍵と同じ鍵を使って検証する署名アルゴリズムを「HS256」、
- 秘密鍵と公開鍵のペアで検証する署名アルゴリズムを「RS256」
と呼びます。
アルゴリズム ... 計算方法のこと。
JWTの注意点
トークンはエンコードされているだけで、暗号化はされていません。
トークンの中身はここ(jwt.io)で誰でも見ることができます。
漏れたらまずい個人情報(メールアドレスなど)はトークンに埋め込まないようにしましょう。
JWTのまとめ
誰でも見れるけど、改ざんできないし、発行者しかデコードできないトークン、それがJWTです。
JWTが理解できる記事たち
次回は?
さて次回は、JWTのログイン認証の流れを理解し、JWTの実装に入ります。
(↓にあります)