29
loading...
This website collects cookies to deliver better user experience
kid
) that specifies which key was used to sign it. The issuer of the JWT provides the JWKS.https://TENANT_DOMAIN/.well-known/jwks.json
, which we will use in step #2 to fetch the public keys to verify the received.module Auth
module VerifyJwt
extend self
JWKS_URL = "https://#{Rails.configuration.auth0[:auth_domain]}/.well-known/jwks.json".freeze
def call(token, audience: :web)
JWT.decode(
token,
nil,
true, # Verify the signature of this token
algorithms: ["RS256"],
iss: "https://#{Rails.configuration.auth0[:auth_domain]}/",
verify_iss: true,
aud: Rails.configuration.auth0[:web_audience],
verify_aud: true,
jwks: fetch_jwks,
)
end
private
def fetch_jwks
response = HTTP.get(JWKS_URL)
if response.code == 200
JSON.parse(response.body.to_s)
end
end
end
end
/jwks.json
endpoint. This introduces a few concerns:/jwks.json
endpoint are a perfect opportunity for caching. The key pair used for signing our JWTs won't change unless they are rotated.module Auth
module VerifyJwt
extend self
JWKS_CACHE_KEY = "auth/jwks-json".freeze
...
def call(token)
JWT.decode(
...
jwks: jwks,
)
end
private
def fetch_jwks
...
end
def jwks
Rails.cache.fetch(JWKS_CACHE_KEY) do
fetch_jwks
end.deep_symbolize_keys
end
end
end
/jwks.json
endpoint again if we recognise a JWT that was signed by a different key than those we know about. We can identify this by:kid
from the JWT headerkeys
of the JWKS, with the matching kid
module Auth
module VerifyJwt
extend self
...
def call(token)
JWT.decode(
...
jwks: jwk_loader,
)
end
private
def jwk_loader
->(options) do
# options[:invalidate] will be `true` if a matching `kid` was not found
# https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/jwk/key_finder.rb#L31
jwks(force: options[:invalidate])
end
end
def fetch_jwks
...
end
def jwks(force: false)
Rails.cache.fetch(JWKS_CACHE_KEY, force: force) do
fetch_jwks
end.deep_symbolize_keys
end
end
end
kid
, we would invalidate our cache every time, forcing us to do an API call..../jwks.json
endpoint fails, maybe due to rate-limiting, we won't have a value to cache and could be caching nil
. This will mean that verifying any JWTs will not be possible until our application can get a successful response from the .../jwks.json
endpoint again.#fetch_jwks
method only returns the JSON response if the request was successful, and nil
otherwise. Meaning we can cater for any failed response by leaving the current value in the cache, so we always have the latest successful response we've received.skip_nil: true
to our fetchmodule Auth
module VerifyJwt
extend self
...
def jwk_loader
->(options) do
jwks(force: options[:invalidate]) || {}
end
end
def jwks(force: false)
Rails.cache.fetch(JWKS_CACHE_KEY, force: force, skip_nil: true) do
fetch_jwks
end&.deep_symbolize_keys
end
end
end
nil
by defaulting to an empty hash ({}
), because the JWT gem expects a hash and not nil.