1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
|
# frozen_string_literal: true
require "jwt"
module OAuth2
module Strategy
# The Client Assertion Strategy
#
# @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-10#section-4.1.3
#
# Sample usage:
# client = OAuth2::Client.new(client_id, client_secret,
# :site => 'http://localhost:8080',
# :auth_scheme => :request_body)
#
# claim_set = {
# :iss => "http://localhost:3001",
# :aud => "http://localhost:8080/oauth2/token",
# :sub => "me@example.com",
# :exp => Time.now.utc.to_i + 3600,
# }
#
# encoding = {
# :algorithm => 'HS256',
# :key => 'secret_key',
# }
#
# access = client.assertion.get_token(claim_set, encoding)
# access.token # actual access_token string
# access.get("/api/stuff") # making api calls with access token in header
#
class Assertion < Base
# Not used for this strategy
#
# @raise [NotImplementedError]
def authorize_url
raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
end
# Retrieve an access token given the specified client.
#
# @param [Hash] claims the hash representation of the claims that should be encoded as a JWT (JSON Web Token)
#
# For reading on JWT and claim keys:
# @see https://github.com/jwt/ruby-jwt
# @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
# @see https://datatracker.ietf.org/doc/html/rfc7523#section-3
# @see https://www.iana.org/assignments/jwt/jwt.xhtml
#
# There are many possible claim keys, and applications may ask for their own custom keys.
# Some typically required ones:
# :iss (issuer)
# :aud (audience)
# :sub (subject) -- formerly :prn https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-06#appendix-F
# :exp, (expiration time) -- in seconds, e.g. Time.now.utc.to_i + 3600
#
# Note that this method does *not* validate presence of those four claim keys indicated as required by RFC 7523.
# There are endpoints that may not conform with this RFC, and this gem should still work for those use cases.
#
# @param [Hash] encoding_opts a hash containing instructions on how the JWT should be encoded
# @option algorithm [String] the algorithm with which you would like the JWT to be encoded
# @option key [Object] the key with which you would like to encode the JWT
#
# These two options are passed directly to `JWT.encode`. For supported encoding arguments:
# @see https://github.com/jwt/ruby-jwt#algorithms-and-usage
# @see https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
#
# The object type of `:key` may depend on the value of `:algorithm`. Sample arguments:
# `get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'})`
# `get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')})`
#
# @param [Hash] request_opts options that will be used to assemble the request
# @option request_opts [String] :scope the url parameter `scope` that may be required by some endpoints
# @see https://datatracker.ietf.org/doc/html/rfc7521#section-4.1
#
# @param [Hash] response_opts this will be merged with the token response to create the AccessToken object
# @see the access_token_opts argument to Client#get_token
def get_token(claims, encoding_opts, request_opts = {}, response_opts = {})
assertion = build_assertion(claims, encoding_opts)
params = build_request(assertion, request_opts)
@client.get_token(params, response_opts)
end
private
def build_request(assertion, request_opts = {})
{
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: assertion,
}.merge(request_opts)
end
def build_assertion(claims, encoding_opts)
raise ArgumentError.new(message: "Please provide an encoding_opts hash with :algorithm and :key") if !encoding_opts.is_a?(Hash) || (%i[algorithm key] - encoding_opts.keys).any?
headers = {}
headers[:kid] = encoding_opts[:kid] if encoding_opts.key?(:kid)
JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm], headers)
end
end
end
end
|