RubyでJWTを扱う

JWTというJSONデータを署名してトークンとして扱う仕組みがあります。 これをRubyで扱う方法を調べてみたので、備忘録がてら書いておきます。


JWTとは

JWTはJSON Web Tokenの略。

JWTが総本山で、 RFC 7519 - JSON Web Token (JWT)に規格が定義されている。

RubyでJWTを扱うには

RubyでJWTを扱うためのgemがいくつかある模様。

この中ではruby-jwtがスタンダードなようなのでこれの使い方を調べてみた。

ruby-jwt

MITライセンスのオープンソース。

https://github.com/jwt/ruby-jwtでコードが公開されている。

使い方

導入

gem install jwt するか、Gemfileに

gem 'jwt'

を追記して、bundle installする。

署名なしJWTを扱う

署名なしのJWTを生成するには、

require 'jwt'
payload = { data: 'test' }
token = JWT.encode payload, nil, 'none'
#=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoiZGF0YSJ9."

とする。 JWT.encodeメソッドの第2引数にnilを指定する。

署名なしのJWTをデコードするには、

decoded = JWT.decode token, nil, false
#=> [{"data"=>"data"}, {"typ"=>"JWT", "alg"=>"none"}]

とする。 元のペイロードが復元されている。

JWT(HMAC)を扱う

HMACで署名したJWTを生成するには、

require 'jwt'
payload = { data: 'test' }
secret  = 'my_secret'
token = JWT.encode payload, secret, 'HS256'
#=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.RbmQ6qKdj8y6GeTBX0EGbLst3wrHuGrNGGOuT5pRPls"

とする。

JWT.encodeメソッドの第2引数にシークレット、第3引数にアルゴリズムを指定する。 デコードするには、

decoded = JWT.decode token, secret, true, { algorithm: 'HS256' }
#=>  [{"data"=>"test"}, {"typ"=>"JWT", "alg"=>"HS256"}]

とする。

JWT(RSA)を扱う

JWTをRSAで署名したJWTを生成するには、

require 'jwt'
payload = { data: 'test' }
rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public  = rsa_private.public_key

token = JWT.encode payload, rsa_private, 'RS256'
#=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.HwhiXp2sZuGf_ZyZV5yCPBQ4Bp_L87XJf4WTK3GmmlEKUMKi_Aro7iLYLvmHNqQyQGt0-d4UKuUGqHQcxbiiktd9YMrES5lJA6ttLKtnrBL7kBA4uM0Y0mLZ6QyiUfXEYMnyEdgiqLguIE50i2nZzALVXVj4PCcySQyhtM0vvblyLxyzmk1fX6tTaok6oNHm2wQO871sYjZKvt8PpFoFhZM81RHyQfezheH6gjcRmfKi8fvPkeTsaTXIKfnkD5cpoDttpOdVTukDxTwGNRfvD-mdw3E9tJcl1ymS6usFsfGwM3HaLNP9DenhmRaicJt5JFCZ_gZty_Cx32BBalfyBw"

とする。RSAの秘密鍵を使って署名する。 デコードするには、

decoded = JWT.decode token, rsa_public, true, { algorithum: 'RS256' }
#=> [{"data"=>"test"}, {"typ"=>"JWT", "alg"=>"RS256"}]

とする。RSAの公開鍵を使ってデコードする。

有効期限の検証

JWTのペイロードには、exp(expiration time=有効期限)を含めることができる。 ruby-jwtでは、JWTのデコード時に、有効期限をチェックすることができる。 (有効期限が満了していたら例外を送出する。)

require 'jwt'

secret  = 'my_secret'
exp     = Time.now.to_i # 現在時刻を期限とする
payload = { data: 'test', exp: exp }
token   = JWT.encode payload, secret, 'HS256'

sleep 1 # 確実に満了させる

decoded = JWT.decode token, secret, true, { :algorithm => 'HS256' }
# => JWT::ExpiredSignature: Signature has expired

expクレームで指定した有効期限を超過したトークンをデコードすると、JWT::ExpiredSignature例外が送出される。

有効期限以外の予約済みクレームに対する検証も簡単に行える模様。 ruby-jwtのREADMEにクレーム単位の検証方法が書いてある。


以上、備忘録です。 JWTって認証関連以外の用途って何かあるのだろうか・・・