¿Descodificaciones dobles para HMAC JWT que utilizan niños?
Estamos intentando usar ruby-jwt para codificar/decodificar JWT firmados HS256 usando kid
s para indicar el registro en un almacén de claves que representa la clave a usar, pero parece que necesitamos hacer 2 decodificaciones para la verificación. Una decodificación que no usa un secreto ni indica a ruby-jwt que queremos verificar el JWT solo para poder eliminarlo kid
, luego una segunda decodificación que pasa el secreto que kid
representa y la bandera para verificar el JWT. Es una cosa menor y b64 es barato, pero ¿hacer 2 decodificaciones es realmente el enfoque que se debe adoptar para los HMAC JWT que contienen kid
? ¿Es esto una limitación de la biblioteca o una falta de conocimiento sobre cómo trabajar con kids
HMAC?
Crear JWT con HS256
keys = {'31b88f20-8edd-49fe-a839-57b8f681888c' => 'mysecret'}
payload = {user_id: 99, kid: keys.first.first, exp: 5.years.from_now.to_i}
token = JWT.encode(payload, keys.first.second, 'HS256')
Verificar JWT
# Get the kid from the jwt so we can look up what secret to use to verify it by the kid
decoded_jwt = JWT.decode(token, nil, false)
kid = decoded_jwt[0]['kid']
secret = keys[kid]
# decode the jwt again with the secret, and verify it
decoded_jwt = JWT.decode(token, secret, true)
# => [{"user_id"=>99, "kid"=>"31b88f20-8edd-49fe-a839-57b8f681888c", "exp"=>1865946624}, {"alg"=>"HS256"}]
Funciona, pero intento ver si hay un enfoque mejor. ¡Gracias!
Puede evitar la doble decodificación utilizando JSON Web Key Set (JWKS).
Para implementar este proceso usando JWKS y HMAC, puede hacerlo de la siguiente manera.
Codificación:
require 'jwt'
secret = 'mysecret'
kid = '31b88f20-8edd-49fe-a839-57b8f681888c'
algorithm = 'HS256'
# Used strings for comparison at the end
# Used Time so it did not rely on Rails
payload = {"user_id" => 99, "exp" => Time.now.to_i + 10000}
# Encoding pass kid as a header field
token = JWT.encode(payload, secret, algorithm , kid: kid)
#=> "eyJraWQiOiIzMWI4OGYyMC04ZWRkLTQ5ZmUtYTgzOS01N2I4ZjY4MTg4OGMiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5OSwiZXhwIjoxNzA4MTE3ODIzfQ.UArtkNyPx0ri08i6D2t7sLtZ_dlqoDczP7r4ZVPtTGM"
Claves de exportación:
Dado que HMAC es una clave simétrica, el tipo de clave ( kty
) debe establecerse en 'oct'. Consulte RFC7518 en 6.1 , y el valor de clave ( k
) debe establecerse en secreto. Consulte RFC7518 en 6.4.1.
#Create a JWK by importing the Hash
jwk = JWT::JWK.new({ kty: 'oct', k: secret, kid: kid, alg: algorithm })
# JSON Web Key Set for advertising your signing keys
jwks_hash = JWT::JWK::Set.new(jwk).export
#=> {:keys=>[{:kty=>"oct", :kid=>"31b88f20-8edd-49fe-a839-57b8f681888c", :alg=>"HS256"}]}
# OR export private keys (Not recommended refer to: https://www.rfc-editor.org/rfc/rfc7517.html#section-9.2)
jwks_hash = JWT::JWK::Set.new(jwk).export(include_private: true)
#=> {:keys=>[{:kty=>"oct", :k=>"mysecret", :kid=>"31b88f20-8edd-49fe-a839-57b8f681888c", :alg=>"HS256"}]}
Descodificación:
# If you did not export the private secret, if you did this step can be skipped
jwks_hash[:keys].each {|k| k.merge!(k: secret)}
# Create a JWKS
jwks = JWT::JWK::Set.new(jwks_hash)
#Decoding
decoded = JWT.decode(token, nil, true, jwks: jwks)
#=> [{"user_id"=>1708119080, "exp"=>1708119627}, {"kid"=>"31b88f20-8edd-49fe-a839-57b8f681888c", "alg"=>"HS256"}]
decoded[0] == payload
#=> true