scinote-web/app/services/api/azure_jwt.rb

59 lines
2.1 KiB
Ruby

module Api
class AzureJwt
require 'jwt'
KEYS_CACHING_PERIOD = 7.days
LEEWAY = 30
def self.fetch_rsa_key(k_id, app_id)
cache_key = "api_azure_ad_rsa_key_#{k_id}"
Rails.cache.fetch(cache_key, expires_in: KEYS_CACHING_PERIOD) do
settings = ApplicationSettings.instance
provider_conf = settings.values['azure_ad_apps']&.find { |v| v['app_id'] == app_id }
raise JWT::VerificationError, 'Azure AD: No application configured with such ID' unless provider_conf
conf_url = provider_conf['conf_url']
keys_url = JSON.parse(Net::HTTP.get(URI(conf_url)))['jwks_uri']
data = JSON.parse(Net::HTTP.get(URI.parse(keys_url)))
verif_key = data['keys'].find { |key| key['kid'] == k_id }
raise JWT::VerificationError, 'Azure AD: No keys from key endpoint match the key in the token' unless verif_key
JSON::JWK.new(verif_key).to_key.to_s
end
end
def self.decode(token)
# First, extract key id from token header,
# [1] is position of the header.
# We will use this ID to fetch correct public key needed for
# verification of the token signature
unverified_token = JWT.decode(token, nil, false)
k_id = unverified_token[1]['kid']
raise JWT::VerificationError, 'Azure AD: No Key ID in token header' unless k_id
# Now search for matching app variables in configuration
app_id = unverified_token[0]['aud']
settings = ApplicationSettings.instance
provider_conf = settings.values['azure_ad_apps']&.find { |v| v['app_id'] == app_id }
raise JWT::VerificationError, 'Azure AD: No application configured with such ID' unless provider_conf
# Decode token payload and verify it's signature.
payload, header = JWT.decode(
token,
OpenSSL::PKey::RSA.new(fetch_rsa_key(k_id, app_id)),
true,
algorithm: 'RS256',
verify_expiration: true,
verify_aud: true,
aud: app_id,
verify_iss: true,
iss: provider_conf['iss'],
nbf_leeway: LEEWAY
)
[HashWithIndifferentAccess.new(payload), HashWithIndifferentAccess.new(header)]
end
end
end