Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve OIDC compliance [SDK-987] #225

Merged
merged 24 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ PATH
remote: .
specs:
auth0 (4.11.0)
jwt (~> 2.2.0)
rest-client (~> 2.0.0)
zache (~> 0.12.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -83,6 +85,7 @@ GEM
concurrent-ruby (~> 1.0)
jaro_winkler (1.5.4)
json (2.3.0)
jwt (2.2.1)
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
Expand Down Expand Up @@ -192,6 +195,7 @@ GEM
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
yard (0.9.25)
zache (0.12.0)
zeitwerk (2.3.0)

PLATFORMS
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,51 @@ In addition to the Management API, this SDK also provides access to [Authenticat

Please note that this module implements endpoints that might be deprecated for newer tenants. If you have any questions about how and when the endpoints should be used, consult the [documentation](https://auth0.com/docs/api/authentication) or ask in our [Community forums](https://community.auth0.com/tags/wordpress).

## ID Token Validation

An ID token may be present in the credentials received after authentication. This token contains information associated with the user that has just logged in, provided the scope used contained `openid`. You can [read more about ID tokens here](https://auth0.com/docs/tokens/concepts/id-tokens).

Before accessing its contents, you must first validate the ID token to ensure it has not been tampered with and that it is meant for your application to consume. Use the `validate_id_token` method to do so:

```ruby
begin
@auth0_client.validate_id_token 'YOUR_ID_TOKEN'
rescue Auth0::InvalidIdToken => e
# In this case the ID Token contents should not be trusted
end
```

The method takes the following optional keyword parameters:

| Parameter | Type | Description | Default value |
| ------------- | -------------- | ------------- | ------------------------- |
| `algorithm` | `JWTAlgorithm` | The [signing algorithm](https://auth0.com/docs/tokens/concepts/signing-algorithms) used by your Auth0 application. | `Auth0::Algorithm::RS256` (using the [JWKS URL](https://auth0.com/docs/tokens/concepts/jwks) of your **Auth0 Domain**) |
| `leeway` | Integer | Number of seconds to account for clock skew when validating the `exp`, `iat` and `azp` claims. | `60` |
| `nonce` | String | The `nonce` value you sent in the call to `/authorize`, if any. | `nil` |
| `max_age` | Integer | The `max_age` value you sent in the call to `/authorize`, if any. | `nil` |
| `issuer` | String | By default the `iss` claim will be checked against the URL of your **Auth0 Domain**. Use this parameter to override that. | `nil` |
| `audience` | String | By default the `aud` claim will be compared to your **Auth0 Client ID**. Use this parameter to override that. | `nil` |

You can check the signing algorithm value under **Advanced Settings > OAuth > JsonWebToken Signature Algorithm** in your Auth0 application settings panel. [We recommend](https://auth0.com/docs/tokens/concepts/signing-algorithms#our-recommendation) that you make use of asymmetric signing algorithms like `RS256` instead of symmetric ones like `HS256`.

```ruby
# HS256

begin
@auth0_client.validate_id_token 'YOUR_ID_TOKEN', algorithm: Auth0::Algorithm::HS256.secret('YOUR_SECRET')
rescue Auth0::InvalidIdToken => e
# Handle error
end

# RS256 with a custom JWKS URL

begin
@auth0_client.validate_id_token 'YOUR_ID_TOKEN', algorithm: Auth0::Algorithm::RS256.jwks_url('YOUR_URL')
rescue Auth0::InvalidIdToken => e
# Handle error
end
```

## Development

In order to set up the local environment you'd have to have Ruby installed and a few global gems used to run and record the unit tests. A working Ruby version can be taken from the [CI script](/.circleci/config.yml). At the moment of this writting we're using Ruby `2.5.7`.
Expand Down
2 changes: 2 additions & 0 deletions auth0.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Gem::Specification.new do |s|
s.require_paths = ['lib']

s.add_runtime_dependency 'rest-client', '~> 2.0.0'
s.add_runtime_dependency 'jwt', '~> 2.2.0'
s.add_runtime_dependency 'zache', '~> 0.12.0'

s.add_development_dependency 'rake', '~> 13.0'
s.add_development_dependency 'fuubar', '~> 2.0'
Expand Down
1 change: 1 addition & 0 deletions lib/auth0.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'auth0/version'
require 'auth0/mixins'
require 'auth0/exception'
require 'auth0/algorithm'
require 'auth0/client'
require 'auth0_client'
# Namespace for ruby-auth0 logic
Expand Down
5 changes: 5 additions & 0 deletions lib/auth0/algorithm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Auth0
module Algorithm
include Auth0::Mixins::Validation::Algorithm
end
end
34 changes: 34 additions & 0 deletions lib/auth0/api/authentication_endpoints.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# frozen_string_literal: true
# rubocop:disable Metrics/ModuleLength

require 'jwt'

module Auth0
module Api
# {https://auth0.com/docs/api/authentication}
Expand Down Expand Up @@ -502,6 +506,36 @@ def unlink_user(access_token, user_id)
post('/unlink', request_params)
end

# Validate an ID token (signature and expiration).
# @see https://auth0.com/docs/tokens/guides/validate-id-tokens
# @param id_token [string] The JWT to validate.
# @param algorithm [JWKAlgorithm] The expected signing algorithm.
# Defaults to +Auth0::Algorithm::RS256.jwks_url("https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json", lifetime: 10 * 60)+.
# @param leeway [integer] The clock skew to accept when verifying date related claims in seconds.
# Must be a non-negative value. Defaults to *60 seconds*.
# @param nonce [string] The nonce value sent during authentication.
# @param max_age [integer] The max_age value sent during authentication.
# Must be a non-negative value.
# @param issuer [string] The expected issuer claim value.
# Defaults to +https://YOUR_AUTH0_DOMAIN/+.
# @param audience [string] The expected audience claim value.
# Defaults to your *Auth0 Client ID*.
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/ParameterLists
def validate_id_token(id_token, algorithm: nil, leeway: 60, nonce: nil, max_age: nil, issuer: nil, audience: nil)
context = {
issuer: issuer || "https://#{@domain}/",
audience: audience || @client_id,
algorithm: algorithm || Auth0::Algorithm::RS256.jwks_url("https://#{@domain}/.well-known/jwks.json"),
leeway: leeway
}

context[:nonce] = nonce unless nonce.nil?
context[:max_age] = max_age unless max_age.nil?

Auth0::Mixins::Validation::IdTokenValidator.new(context).validate(id_token)
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/ParameterLists

private

# Build a URL query string from a hash.
Expand Down
2 changes: 2 additions & 0 deletions lib/auth0/exception.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ def reset
Time.at(headers['X-RateLimit-Reset']).utc
end
end

class InvalidIdToken < Auth0::Exception; end
end
Loading