Skip to content

Commit

Permalink
Parsing claims from the id_token (#120)
Browse files Browse the repository at this point in the history
When an id_token is provided, claims should be pulled from it, versus requesting them from the userinfo endpoint
  • Loading branch information
davidpatrick authored Jan 20, 2021
1 parent 68f0d52 commit a6f5220
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 34 deletions.
39 changes: 26 additions & 13 deletions lib/omniauth/auth0/jwt_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,33 @@ def initialize(options, authorize_params = {})
end

# Verify a token's signature. Only tokens signed with the RS256 or HS256 signatures are supported.
# Deprecated: Please use `decode` instead
# @return array - The token's key and signing algorithm
def verify_signature(jwt)
head = token_head(jwt)

# Make sure the algorithm is supported and get the decode key.
if head[:alg] == 'RS256'
key, alg = [rs256_decode_key(head[:kid]), head[:alg]]
elsif head[:alg] == 'HS256'
key, alg = [@client_secret, head[:alg]]
else
raise OmniAuth::Auth0::TokenValidationError.new("Signature algorithm of #{head[:alg]} is not supported. Expected the ID token to be signed with RS256 or HS256")
end
key, alg = extract_key(head)

# Call decode to verify the signature
JWT.decode(jwt, key, true, decode_opts(alg))

return key, alg
end

# Decodes a JWT and verifies it's signature. Only tokens signed with the RS256 or HS256 signatures are supported.
# @param jwt string - JWT to verify.
# @return hash - The decoded token, if there were no exceptions.
# @see https://github.com/jwt/ruby-jwt
def decode(jwt)
head = token_head(jwt)
key, alg = extract_key(head)

# Call decode to verify the signature
JWT.decode(jwt, key, true, decode_opts(alg))
end

# Verify a JWT.
# @param jwt string - JWT to verify.
# @param authorize_params hash - Authorization params to verify on the JWT
# @return hash - The verified token, if there were no exceptions.
# @return hash - The verified token payload, if there were no exceptions.
def verify(jwt, authorize_params = {})
if !jwt
raise OmniAuth::Auth0::TokenValidationError.new('ID token is required but missing')
Expand All @@ -62,8 +66,7 @@ def verify(jwt, authorize_params = {})
raise OmniAuth::Auth0::TokenValidationError.new('ID token could not be decoded')
end

key, alg = verify_signature(jwt)
id_token, header = JWT.decode(jwt, key, false)
id_token, header = decode(jwt)
verify_claims(id_token, authorize_params)

return id_token
Expand Down Expand Up @@ -116,6 +119,16 @@ def decode_opts(alg)
}
end

def extract_key(head)
if head[:alg] == 'RS256'
key, alg = [rs256_decode_key(head[:kid]), head[:alg]]
elsif head[:alg] == 'HS256'
key, alg = [@client_secret, head[:alg]]
else
raise OmniAuth::Auth0::TokenValidationError.new("Signature algorithm of #{head[:alg]} is not supported. Expected the ID token to be signed with RS256 or HS256")
end
end

def rs256_decode_key(kid)
jwks_x5c = jwks_key(:x5c, kid)

Expand Down
19 changes: 15 additions & 4 deletions lib/omniauth/strategies/auth0.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ def client
auth_scope = session_authorize_params[:scope]
if auth_scope.respond_to?(:include?) && auth_scope.include?('openid')
# Make sure the ID token can be verified and decoded.
auth0_jwt = OmniAuth::Auth0::JWTValidator.new(options)
auth0_jwt.verify(credentials['id_token'], session_authorize_params)
jwt_validator.verify(credentials['id_token'], session_authorize_params)
end

credentials
Expand Down Expand Up @@ -130,11 +129,23 @@ def callback_phase
end

private
def jwt_validator
@jwt_validator ||= OmniAuth::Auth0::JWTValidator.new(options)
end

# Parse the raw user info.
def raw_info
userinfo_url = options.client_options.userinfo_url
@raw_info ||= access_token.get(userinfo_url).parsed
return @raw_info if @raw_info

if access_token["id_token"]
claims, header = jwt_validator.decode(access_token["id_token"])
@raw_info = claims
else
userinfo_url = options.client_options.userinfo_url
@raw_info = access_token.get(userinfo_url).parsed
end

return @raw_info
end

# Check if the options include a client_id
Expand Down
39 changes: 22 additions & 17 deletions spec/omniauth/strategies/auth0_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
end

describe 'client_options' do
let(:subject) { auth0.client }
let(:subject) { OmniAuth::Strategies::Auth0.new(
application,
client_id,
client_secret,
domain_url
).client }

context 'domain with https' do
let(:domain_url) { 'https://samples.auth0.com' }
Expand Down Expand Up @@ -168,12 +173,17 @@
payload['sub'] = user_id
payload['iss'] = "#{domain_url}/"
payload['aud'] = client_id
payload['name'] = name
payload['nickname'] = nickname
payload['picture'] = picture
payload['email'] = email
payload['email_verified'] = email_verified

JWT.encode payload, client_secret, 'HS256'
end

let(:oauth_response) do
{
id_token: id_token,
access_token: access_token,
expires_in: expires_in,
token_type: token_type
Expand All @@ -189,17 +199,7 @@
}
end

let(:basic_user_info) { { sub: user_id } }
let(:oidc_user_info) do
{
sub: user_id,
name: name,
nickname: nickname,
email: email,
picture: picture,
email_verified: email_verified
}
end
let(:basic_user_info) { { "sub" => user_id, "name" => name } }

def stub_auth(body)
stub_request(:post, 'https://samples.auth0.com/oauth/token')
Expand Down Expand Up @@ -227,7 +227,9 @@ def trigger_callback
WebMock.reset!
end

let(:subject) { MultiJson.decode(last_response.body) }
let(:subject) do
MultiJson.decode(last_response.body)
end

context 'basic oauth' do
before do
Expand All @@ -246,10 +248,14 @@ def trigger_callback
expect(subject['credentials']['expires_at']).to_not be_nil
end

it 'has basic values' do
it 'has basic values' do
expect(subject['provider']).to eq('auth0')
expect(subject['uid']).to eq(user_id)
expect(subject['info']['name']).to eq(user_id)
expect(subject['info']['name']).to eq(name)
end

it 'should use the user info endpoint' do
expect(subject['extra']['raw_info']).to eq(basic_user_info)
end
end

Expand All @@ -275,7 +281,6 @@ def trigger_callback
context 'oidc' do
before do
stub_auth(oidc_response)
stub_userinfo(oidc_user_info)
trigger_callback
end

Expand Down

0 comments on commit a6f5220

Please sign in to comment.