1
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?

RailsでCognitoのJWT署名検証を行う方法:RSA公開鍵の取得と検証

Last updated at Posted at 2025-12-31

はじめに

RailsでCognitoのJWTトークンの署名を検証する方法について解説します。
Cognitoが提供するJWK(JSON Web Key)形式の公開鍵をRSA形式で読み込み、JWTの署名検証を行います。
今回はCognitoの設定やトークンの取得方法には触れておりません。

環境

本実装は、以下の環境を前提としています。

Ruby: 3.2.2

Ruby on Rails: 7.0.2

手順

  1. 環境変数の設定
  2. 公開鍵を取得
  3. JWK形式からRSA公開鍵形式に変更
  4. JWT署名検証

環境変数の設定

Cognitoユーザープールを設定し、以下の情報を環境変数に設定します。

リージョン
クライアントID
ユーザープールID

AWS_REGION=your-region
COGNITO_CLIENT_ID=your-client-id
COGNITO_USER_POOL_ID=your-user-pool-id

CognitoのJWKs(JSON Web Key Set)を取得

Cognitoが提供するJWK形式の公開鍵セットを取得します。
以下のメソッドでは、環境変数を用いてCognitoから公開鍵を取得し、JSON形式で返します。

  # Cognitoの公開鍵をJWKS形式で提供するURL
  JWK_URL = "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['COGNITO_USER_POOL_ID']}/.well-known/jwks.json"
  def self.get_cognito_public_keys
    uri = URI(JWK_URL)
    response = Net::HTTP.get(uri)
    JSON.parse(response)['keys']
  end

JWK形式からRSA公開鍵形式に変更

Cognitoから提供される公開鍵は、JWK (JSON Web Key) 形式であり、JSON形式で公開鍵情報(kty, kid, n, e などの属性)を持っています。
JWK形式はWeb上で公開鍵を配布するための一般的な形式ですが、直接的に暗号ライブラリ(この場合 OpenSSL)で署名の検証に使用できません。
そのため、公開鍵をJWK形式からRSA公開鍵形式に変換します。

# JWKSの取得とキャッシュ管理
def self.get_cognito_public_keys
  # RailsキャッシュからJWKを取得し、あればそれを返す
  cached_keys = Rails.cache.read('cognito_jwk_keys')
  return cached_keys if cached_keys

  # JWKを再取得し、キャッシュに保存
  uri = URI(JWK_URL)
  response = Net::HTTP.get(uri)
  keys = JSON.parse(response)['keys']

  # RailsキャッシュにJWKを保存し、有効期限を1時間後に設定
  Rails.cache.write('cognito_jwk_keys', keys, expires_in: 1.hour)

  keys
end

JWT署名の検証

次にJWTトークンをデコードし、署名を検証した上でトークンのペイロード部分だけを返す関数を作成します。

def self.verify_token(token)
  # トークンヘッダから公開鍵の kid を取得
  header = JWT.decode(token, nil, false).last
  kid = header['kid']

  # 公開鍵を取得し、トークンの署名検証
  public_key = find_public_key(kid)
  JWT.decode(token, public_key, true, {
    iss: "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['COGNITO_USER_POOL_ID']}",
    verify_iss: true,
    algorithms: ['RS256']
  }).first
end

メソッドの説明

header = JWT.decode(token, nil, false).last

トークンをデコードしてヘッダー部分を取得します。この中からkidを使い、該当する公開鍵を探します。

public_key = find_public_key(kid)

kidに対応する公開鍵を取得し、RSA形式に変換して返します。

JWT.decode(token, public_key, true, {...})

公開鍵で署名の有効性を検証します。ここで検証されるのは、トークンの発行者(iss)がCognitoのユーザープールと一致し、アルゴリズムがRS256であることです。

ソースコード

JWK_URL = "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['COGNITO_USER_POOL_ID']}/.well-known/jwks.json"

def self.verify_token(token)
  begin
    # トークンのヘッダーをデコードし、公開鍵のkid(キーID)を取得
    header = JWT.decode(token, nil, false).last
    kid = header['kid']

    # JWKから公開鍵を取得
    public_key = find_public_key(kid)

    # トークンの検証
    decoded_token = JWT.decode(token, public_key, true, {
      iss: "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['COGNITO_USER_POOL_ID']}",
      verify_iss: true,
      algorithms: ['RS256']
    }).first

    # デコードされたトークンを返す
    decoded_token

  rescue JWT::DecodeError => e
    Rails.logger.error "トークンの検証に失敗しました: #{e.message}"
    nil
  end
end

# kid に対応する公開鍵を取得
def self.find_public_key(kid)
  keys = get_cognito_public_keys
  jwk = keys.find { |key| key['kid'] == kid }

  raise "公開鍵が見つかりません (kid: #{kid})" unless jwk

  
  n = OpenSSL::BN.new(decode_base64url(jwk['n']), 2)
  e = OpenSSL::BN.new(decode_base64url(jwk['e']), 2)

  # ASN.1 DER形式で公開鍵を構築
  sequence = OpenSSL::ASN1::Sequence([
    OpenSSL::ASN1::Integer.new(n),
    OpenSSL::ASN1::Integer.new(e)
  ])

  rsa_public_key = OpenSSL::PKey::RSA.new(sequence.to_der) # 公開鍵生成
end

# JWKSの取得とキャッシュ管理
def self.get_cognito_public_keys
  # RailsキャッシュからJWKを取得し、あればそれを返す
  cached_keys = Rails.cache.read('cognito_jwk_keys')
  return cached_keys if cached_keys

  # JWKを再取得し、キャッシュに保存
  uri = URI(JWK_URL)
  response = Net::HTTP.get(uri)
  keys = JSON.parse(response)['keys']

  # RailsキャッシュにJWKを保存し、有効期限を1時間後に設定
  Rails.cache.write('cognito_jwk_keys', keys, expires_in: 1.hour)

  keys
end

# Base64URL形式のデコード
def self.decode_base64url(str)
  Base64.urlsafe_decode64(str + padding)
end

まとめ

この記事では、RailsでCognitoのJWTトークンの署名検証を行う方法について解説しました。この記事を読んでいただいた方の役に立ててば幸いです

参考

https://github.com/jwt/ruby-jwt
https://zenn.dev/ndjndj/articles/1a2c2a8f803dc7

1
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
1
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?