0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby, RailsにおけるJWT

Last updated at Posted at 2025-03-29

JWTについてまっっっっっっっっっっっっっっっっっったくわからず調べたので備忘録としてアウトプットします。
※随時更新予定

誤った内容があればマサカリお願いします。血まみれになりたいsです。

JWTとは

Json Web Tokenを略してJWT

JWTでは、トークン内に任意の情報(クレーム)を保持することが可能であり、例えばサーバはクライアントに対して「管理者としてログイン済」という情報を含んだトークンを生成することができる。クライアントはそのトークンを、自身が管理者としてログイン済であることの証明に使用することができる。トークンは当事者の一方(通常はサーバ)または両方(もう一方は公開鍵を提供する)の秘密鍵により署名されており、発行されたトークンが正規のものか確認することができる。

トークン :JSONオブジェクトをエンコードした文字列

eyJhbGciOiJIUzI1NiJ9. # ヘッダー
eyJleHAiOjE2MDA1OTY0MzEsInN1YiI6MSwibmFtZSI6InVzZXIwIn0. # ペイロード
lXcwASyLX5GEsMvPYDVhe0ovJj631fUiC0q2ojK-yK0 # 署名

JWT(トークン)の構造

JWT(トークン)は3つの構造からなる

1. ヘッダー

トークンのタイプと使用する署名アルゴリズム

eyJhbGciOiJIUzI1NiJ9. # ヘッダー
=> { "alg": "HS256", "typ": "JWT" }

2. ペイロード

実際のデータを含むクレーム(任意の情報)

eyJleHAiOjE2MDA1OTY0MzEsInN1YiI6MSwibmFtZSI6InVzZXIwIn0. # ペイロード
=> {"exp"=>1600596431, "sub"=>1, "name"=>"user0"}

expsub などのそれぞれの値をクレームと呼ぶ

デフォルトで指定されている値を**「予約済クレーム」、使用者が任意に指定した値を「公開(パブリック)クレーム」、当事者間で合意された非公開のカスタムクレームを「プライベートクレーム」**と呼ぶ

  • exp(Expiration Time)
    • トークンの有効期限(失効時間)
    • UNIXタイムスタンプ形式
    • 例: "exp": 1622505600 (2021年6月1日)
  • sub(Subject)
    • トークンの主題(通常はユーザーID)
    • 文字列形式
    • 例: "sub": "1234567890" または "sub": "user@example.com"
  • iss (Issuer)
    • トークンの発行者
    • 文字列形式
    • 例: "iss": "https://api.myapp.com"

その他のクレーム

3. 署名

トークンの完全性を検証するのに使用(トークンが変更されていないか確認するために使用)

// エンコード => デコード
lXcwASyLX5GEsMvPYDVhe0ovJj631fUiC0q2ojK-yK0
=> HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

主な用途

認証

ユーザーがログインしたあと、以降のリクエストにJWTが含まれ、そのトークンにより保護されたルートやリソースへのアクセスが許可される

情報交換

署名されているため、送信者が本人であることを確認でき、情報が改ざんされていないことを確認できる

RubyにおけるJWT

Gemとして提供されているjwt はJSON Web Tokenの実装を提供している。このgemを使用することで、JWTの生成と検証が簡単に行える。

require "jwt"

# トークンのエンコード(生成)
payload = { data: "test" }
token = JWT.encode payload, "my_secret", "HS256"

# トークンのデコード
decoded_token = JWT.decode token, "my_secret", true, { algorithm: "HS256" }
# トークン生成
def generate_token(user_id)
	pay_load = { user_id: user_id, exp: 24.hours.from_now.to_i }
	JWT.encode payload, Rails.application.secrets.secret_ley_base, "HS256"
end

# トークン検証
def authenticate_user
	token = request.headers["Authorization"]&.split(" ")&.last
	begin
		decoded = JWT.decode token, Rails.application.secrets.secret_key_base, true, { algorithm: "HS256" }
		@current_user = User.find(decoded[0]["user_id"])
	rescue JWT::DecodeError
		render json: { errors: ["Invalid token"] }, status: unauthrized
	rescue JWT::ExpiredSignature
    render json: { errors: ['Token has expired'] }, status: :unauthorized
  end
end

RailsにおけるJWTの役割

1. APIにおける認証の実装

Railsでは特にフロントエンドとバックエンドが分離されたSPAやモバイルアプリとの連携において、JWTが重要な役割を果たす。

def login
	user = User.find_by(email: params[:email])
	if user&.authenticate(params[:password])
		token = 
		JWT.encode({user_id: user.id, exp: 24.hours.from_now.to_i},
			Rails.application.credentials.secret.secret_key_base)
		render json: { token: token, user: user.as_json(expect: :password_digest) }
	else
		render json: { error: "Invalid credentials" }, status: unauthorized
	end
end

2.セッションレス認証の実現

従来Railsではcookieベースのセッション管理が一般的だったが、JWTを使用することで

  • サーバー側でセッション状態を保持する必要がなくなる(ステートレス)
  • スケーラビリティ向上
  • クロスドメイン認証の容易さ

というメリットが生まれる。

3.マイクロサービスアーキテクチャでの認証

Railsアプリケーションが複数のマイクロサービスと連携する場合、JWTは各サービスの間の認証に使われる

# サービスA(ユーザー認証)
token = JWT.encode({user_id: user.id, permissions: user.permissions}, secret_key)

# サービスB(リソースサーバー)
begin
  payload = JWT.decode(token, secret_key, true, algorithm: 'HS256').first
  user_permissions = payload['permissions']
  # 権限に基づいた処理...
rescue JWT::DecodeError
  # 認証エラー処理
end

JWTのいいところ

情報改ざんができない

トークンが正しいものか検証する際に署名アルゴリズムと一致したトークンでなければエラーが発生する。

Userテーブルにトークンを一時的に保有する絡むを作成しなくてもよくなる

署名をした鍵を持つものしかトークンを検証することができない

セキュリティ上の考慮点

  • トークンはエンコードされているだけで暗号化されているわけではない
  • トークンの有効期限を適切に設定(expクレーム)
  • 機密情報をペイロードに含めない
  • HTTPS通信
  • 定期的な鍵のローテーション

参考記事

特に関係ないけど出てきたわからないコード

HashWithIndifferentAccess.new(decoded)

HashWithIndifferentAccess

Rails(activesupport)で提供されているHashのサブクラス

SybolとStringを区別せず同一のキーとして取り扱う

hogehoge(dev)> normal_hash = { "user_id" => 1 }
=> {"user_id"=>1}
hogehoge(dev)> normal_hash["user_id"]
=> 1
hogehoge(dev)> normal_hash[:user_id]
=> nil
hogehoge(dev)> indifferent_hash = HashWithIndifferentAccess.new({ "user_id" => 1 })
=> {"user_id"=>1}
hogehoge(dev)> indifferent_hash["user_id"]
=> 1
hogehoge(dev)> indifferent_hash[:user_id]
=> 1
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?