class OmniAuth::Strategies::AzureActiveDirectoryB2C
A strategy for authentication against Azure Active Directory.
Constants
- DEFAULT_RESPONSE_MODE
- DEFAULT_RESPONSE_TYPE
uid {
(JWT.decode(request.params['id_token'], nil, false).first)['sub']
}
info do
{ #name: raw_info['name'], #nickname: raw_info['unique_name'], #first_name: raw_info['given_name'], #last_name: raw_info['family_name'], #email: raw_info['email'] || raw_info['upn'], #oid: raw_info['oid'], #tid: raw_info['tid'] }
end
Public Instance Methods
Overridden method from OmniAuth::Strategy. This is the second step in the authentication process. It is called after the user enters credentials at the authorization endpoint.
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 97 def callback_phase #raise request.params.inspect error = request.params['error_reason'] || request.params['error'] fail(OAuthError, error) if error @id_token = request.params['id_token'] @code = request.params['code'] @claims, @header = validate_and_parse_id_token(@id_token) validate_chash(@code, @claims, @header) super end
Overridden method from OmniAuth::Strategy. This is the first step in the authentication process.
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 89 def request_phase redirect authorize_endpoint_url end
The client id (key) and tenant must be configured when the OmniAuth
middleware is installed. Example:
require 'omniauth' require 'omniauth-azure-activedirectory' use OmniAuth::Builder do provider :azure_activedirectory, ENV['AAD_KEY'], ENV['AAD_TENANT'] end
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 48 args [:client_id, :tenant, :policy, :scope]
Private Instance Methods
The client id of the calling application. This must be configured where AzureAD is installed as an OmniAuth
strategy.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 132 def client_id return options.client_id if options.client_id fail StandardError, 'No client_id specified in AzureAD configuration.' end
Fetches the OpenId Connect configuration for the AzureAD tenant. This contains several import values, including:
authorization_endpoint token_endpoint token_endpoint_auth_methods_supported jwks_uri response_types_supported response_modes_supported subject_types_supported id_token_signing_alg_values_supported scopes_supported issuer claims_supported microsoft_multi_refresh_token check_session_iframe end_session_endpoint userinfo_endpoint
@return Hash
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 181 def fetch_openid_config JSON.parse(Net::HTTP.get(URI(openid_config_url))) rescue JSON::ParserError raise StandardError, 'Unable to fetch OpenId configuration for ' \ 'AzureAD tenant.' end
Fetches the current signing keys for Azure AD. Note that there should always two available, and that they have a 6 week rollover.
Each key is a hash with the following fields:
kty, use, kid, x5t, n, e, x5c
@return Array
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 153 def fetch_signing_keys response = JSON.parse(Net::HTTP.get(URI(signing_keys_url))) response['keys'] rescue JSON::ParserError raise StandardError, 'Unable to fetch AzureAD signing keys.' end
The expected id token issuer taken from the discovery endpoint.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 141 def issuer openid_config['issuer'] end
Generates a new nonce for one time use. Stores it in the session so multiple users don't share nonces. All nonces should be generated by this method.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 194 def new_nonce session['omniauth-azure-activedirectoryb2c.nonce'] = SecureRandom.uuid end
A memoized version of fetch_openid_config
.
@return Hash
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 202 def openid_config @openid_config ||= fetch_openid_config end
The location of the OpenID configuration for the tenant.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 210 def openid_config_url "https://login.microsoftonline.com/#{tenant}/v2.0/.well-known/openid-configuration" end
The policy of the calling application. Note that this must be explicitly configured when installing the AzureAD OmniAuth
strategy.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 278 def policy return options.policy if options.policy fail StandardError, 'No policy specified in AzureAD configuration.' end
Returns the most recent nonce for the session and deletes it from the session.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 219 def read_nonce session.delete('omniauth-azure-activedirectory.nonce') end
The response_mode
that will be set in the authorization request query parameters. Can be overridden by the client, but it shouldn't need to be.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 239 def response_mode options[:response_mode] || DEFAULT_RESPONSE_MODE end
The response_type
that will be set in the authorization request query parameters. Can be overridden by the client, but it shouldn't need to be.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 229 def response_type options[:response_type] || DEFAULT_RESPONSE_TYPE end
The policy of the calling application. Note that this must be explicitly configured when installing the AzureAD OmniAuth
strategy.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 289 def scope return options.scope if options.scope fail StandardError, 'No scope specified in AzureAD configuration.' end
The keys used to sign the id token JWTs. This is just a memoized version of fetch_signing_keys
.
@return Array
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 248 def signing_keys @signing_keys ||= fetch_signing_keys end
The location of the public keys of the token signer. This is parsed from the OpenId config response.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 257 def signing_keys_url return openid_config['jwks_uri'] if openid_config.include? 'jwks_uri' fail StandardError, 'No jwks_uri in OpenId config response.' end
The tenant of the calling application. Note that this must be explicitly configured when installing the AzureAD OmniAuth
strategy.
@return String
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 267 def tenant return options.tenant if options.tenant fail StandardError, 'No tenant specified in AzureAD configuration.' end
Verifies the signature of the id token as well as the exp, nbf, iat, iss, and aud fields.
See OpenId Connect Core 3.1.3.7 and 3.2.2.11.
@return Claims, Header
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 303 def validate_and_parse_id_token(id_token) # The second parameter is the public key to verify the signature. # However, that key is overridden by the value of the executed block # if one is present. # # If you're thinking that this looks ugly with the raw nil and boolean, # see https://github.com/jwt/ruby-jwt/issues/59. jwt_claims, jwt_header = JWT.decode(id_token, nil, false) #do |header| # There should always be one key from the discovery endpoint that # matches the id in the JWT header. # x5c = (signing_keys.find do |key| # key['kid'] == header['kid'] # end || {})['x5c'] # if x5c.nil? || x5c.empty? # fail JWT::VerificationError, # 'No keys from key endpoint match the id token' # end # # The key also contains other fields, such as n and e, that are # # redundant. x5c is sufficient to verify the id token. # OpenSSL::X509::Certificate.new(JWT.base64url_decode(x5c.first)).public_key #end return jwt_claims, jwt_header end
Verifies that the c_hash the id token claims matches the authorization code. See OpenId Connect Core 3.3.2.11.
@param String code @param Hash claims @param Hash header
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 334 def validate_chash(code, claims, header) # This maps RS256 -> sha256, ES384 -> sha384, etc. algorithm = (header['alg'] || 'RS256').sub(/RS|ES|HS/, 'sha') full_hash = OpenSSL::Digest.new(algorithm).digest code c_hash = JWT.base64url_encode full_hash[0..full_hash.length / 2 - 1] return if c_hash == claims['c_hash'] fail JWT::VerificationError, 'c_hash in id token does not match auth code.' end
The options passed to the Ruby JWT library to verify the id token. Note that these are not all the checks we perform. Some (like nonce) are not handled by the JWT API and are checked manually in validate_and_parse_id_token
.
@return Hash
# File lib/omniauth/strategies/azure_activedirectoryb2c.rb, line 357 def verify_options { verify_expiration: true, verify_not_before: true, verify_iat: true, verify_iss: true, 'iss' => issuer, verify_aud: true, 'aud' => client_id } end