omniauth-auth0 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +39 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- data/.gitignore +5 -1
- data/CHANGELOG.md +49 -1
- data/CODE_OF_CONDUCT.md +3 -0
- data/CONTRIBUTING.md +71 -0
- data/Gemfile +3 -4
- data/README.md +94 -82
- data/Rakefile +2 -2
- data/lib/omniauth-auth0/version.rb +1 -1
- data/lib/omniauth/auth0/jwt_validator.rb +122 -0
- data/lib/omniauth/strategies/auth0.rb +44 -10
- data/omniauth-auth0.gemspec +3 -3
- data/spec/omniauth/auth0/jwt_validator_spec.rb +304 -0
- data/spec/omniauth/strategies/auth0_spec.rb +22 -1
- data/spec/resources/jwks.json +28 -0
- data/spec/spec_helper.rb +2 -2
- metadata +16 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 934d8fc4286b19102f56b0ac05dce7cc655c4d93dc7e4808bcec7e208542ddce
|
4
|
+
data.tar.gz: 9fc0b0a4060f90da65113c2123c69eb9d04566910afe942f7f9eb5bc0a172afa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bc7c98c7a6b411e2e1572a61e143ba949a02000ce153aa864ffd9c5211d7c36a32bbf6394c846e50c42d39836b2bee2d6ed4bd1d805f780d22e708a3597ca5c
|
7
|
+
data.tar.gz: 0c2ba38df3e347b84c61e97db2b2d78a6aa1a3eabb5de04b27fb9dd1f0a04f60c6eb8858d1171f90b06ca4417e166bd1f9ab8fa3e49512029f7624b5636123db
|
@@ -0,0 +1,39 @@
|
|
1
|
+
In order to efficiently and accurately address your issue or feature request, please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. Please delete any sections or questions below that do not pertain to this request.
|
2
|
+
|
3
|
+
For general support or usage questions, please use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com.).
|
4
|
+
|
5
|
+
### Description
|
6
|
+
|
7
|
+
Description of the bug or feature request and why it's a problem. Consider including:
|
8
|
+
|
9
|
+
- The use case or overall problem you're trying to solve
|
10
|
+
- Information about when the problem started
|
11
|
+
|
12
|
+
### Prerequisites
|
13
|
+
|
14
|
+
* [ ] I have read the [Auth0 contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
|
15
|
+
* [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
|
16
|
+
* [ ] Did you check the [documentation](https://auth0.com/docs/quickstart/webapp/rails)?
|
17
|
+
* [ ] Did you check [Auth0 Community](https://community.auth0.com/tags/rails)?
|
18
|
+
* [ ] Are you reporting this to the correct repository? This strategy relies on [OmniAuth](https://github.com/omniauth/omniauth) and the [OmniAuth OAuth2](https://github.com/omniauth/omniauth-oauth2) strategy.
|
19
|
+
* [ ] Are there any related or duplicate [Issues](https://github.com/auth0/omniauth-auth0/issues) or [PRs](https://github.com/auth0/omniauth-auth0/pulls) for this issue?
|
20
|
+
|
21
|
+
### Environment
|
22
|
+
|
23
|
+
Please provide the following:
|
24
|
+
|
25
|
+
* OmniAuth-Auth0 version:
|
26
|
+
* Ruby version:
|
27
|
+
* Rails veresion:
|
28
|
+
* Browser version, if applicable:
|
29
|
+
* Additional gems that might be affecting your instance:
|
30
|
+
|
31
|
+
### Reproduction
|
32
|
+
|
33
|
+
Detail the steps taken to reproduce this error and note if this issue can be reproduced consistently or if it is intermittent.
|
34
|
+
|
35
|
+
Please include:
|
36
|
+
|
37
|
+
- Log files (redact/remove sensitive information)
|
38
|
+
- Application settings (redact/remove sensitive information)
|
39
|
+
- Screenshots, if helpful
|
@@ -0,0 +1,32 @@
|
|
1
|
+
### Changes
|
2
|
+
|
3
|
+
Please describe both what is changing and why this is important. Include:
|
4
|
+
|
5
|
+
- Endpoints added, deleted, deprecated, or changed
|
6
|
+
- Classes and methods added, deleted, deprecated, or changed
|
7
|
+
- Screenshots of new or changed UI, if applicable
|
8
|
+
- A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released)
|
9
|
+
|
10
|
+
### References
|
11
|
+
|
12
|
+
Please include relevant links supporting this change such as a:
|
13
|
+
|
14
|
+
- support ticket
|
15
|
+
- community post
|
16
|
+
- StackOverflow post
|
17
|
+
- support forum thread
|
18
|
+
- related GitHub issue in this or another repo
|
19
|
+
|
20
|
+
### Testing
|
21
|
+
|
22
|
+
Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors.
|
23
|
+
|
24
|
+
* [ ] This change adds unit test coverage
|
25
|
+
* [ ] This change has been tested on the latest version of the platform/language or why not
|
26
|
+
|
27
|
+
### Checklist
|
28
|
+
|
29
|
+
* [ ] I have read the [Auth0 contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
|
30
|
+
* [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
|
31
|
+
* [ ] All existing and new tests complete without errors
|
32
|
+
* [ ] All code quality tools/guidelines in the [CONTRIBUTING documentation](CONTRIBUTING.md) have been run/followed
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,33 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v2.1.0](https://github.com/auth0/omniauth-auth0/tree/v2.1.0) (2018-10-30)
|
4
|
+
[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.0.0...v2.1.0)
|
5
|
+
|
6
|
+
**Closed issues**
|
7
|
+
- URL should be spelled uppercase outside of code [\#64](https://github.com/auth0/omniauth-auth0/issues/64)
|
8
|
+
- Add prompt=none authorization param handler [\#58](https://github.com/auth0/omniauth-auth0/issues/58)
|
9
|
+
- Could not find a valid mapping for path "/auth/oauth2/callback" [\#56](https://github.com/auth0/omniauth-auth0/issues/56)
|
10
|
+
- I had to downgrade my gems to use this strategy :-( [\#53](https://github.com/auth0/omniauth-auth0/issues/53)
|
11
|
+
- CSRF detected [\#49](https://github.com/auth0/omniauth-auth0/issues/49)
|
12
|
+
- /auth/:provider route not registered? [\#47](https://github.com/auth0/omniauth-auth0/issues/47)
|
13
|
+
|
14
|
+
**Added**
|
15
|
+
- Add ID token validation [\#62](https://github.com/auth0/omniauth-auth0/pull/62) ([joshcanhelp](https://github.com/joshcanhelp))
|
16
|
+
- Silent authentication [\#59](https://github.com/auth0/omniauth-auth0/pull/59) ([batalla3692](https://github.com/batalla3692))
|
17
|
+
- Pass connection parameter to auth0 [\#54](https://github.com/auth0/omniauth-auth0/pull/54) ([tomgi](https://github.com/tomgi))
|
18
|
+
|
19
|
+
**Changed**
|
20
|
+
- Update to omniauth-oauth2 [\#55](https://github.com/auth0/omniauth-auth0/pull/55) ([chills42](https://github.com/chills42))
|
21
|
+
|
22
|
+
**Fixed**
|
23
|
+
- Fix Rubocop errors [\#66](https://github.com/auth0/omniauth-auth0/pull/66) ([joshcanhelp](https://github.com/joshcanhelp))
|
24
|
+
- Fix minute bug in README.md [\#63](https://github.com/auth0/omniauth-auth0/pull/63) ([rahuldess](https://github.com/rahuldess))
|
25
|
+
|
3
26
|
## [v2.0.0](https://github.com/auth0/omniauth-auth0/tree/v2.0.0) (2017-01-25)
|
4
27
|
[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v2.0.0)
|
5
28
|
|
6
29
|
Updated library to handle OIDC conformant clients and OAuth2 features in Auth0.
|
7
|
-
This affects how the `credentials` and `info` attributes are populated since the payload of /oauth/token and /userinfo are
|
30
|
+
This affects how the `credentials` and `info` attributes are populated since the payload of /oauth/token and /userinfo are different when using OAuth2/OIDC features.
|
8
31
|
|
9
32
|
The `credentials` hash will always have an `access_token` and might have a `refresh_token` (if it's allowed in your API settings in Auth0 dashboard and requested using `offline_access` scope) and an `id_token` (scope `openid` is needed for Auth0 to return it).
|
10
33
|
|
@@ -17,6 +40,31 @@ The `info` object will use the [OmniAuth schema](https://github.com/omniauth/omn
|
|
17
40
|
|
18
41
|
Also in `extra` will have in `raw_info` the full /userinfo response.
|
19
42
|
|
43
|
+
**Fixed**
|
44
|
+
- Use image attribute of omniauth instead of picture [\#45](https://github.com/auth0/omniauth-auth0/pull/45) ([hzalaz](https://github.com/hzalaz))
|
45
|
+
- Rework strategy to handle OAuth and OIDC [\#44](https://github.com/auth0/omniauth-auth0/pull/44) ([hzalaz](https://github.com/hzalaz))
|
46
|
+
- lock v10 update, dependencies update [\#41](https://github.com/auth0/omniauth-auth0/pull/41) ([Amialc](https://github.com/Amialc))
|
47
|
+
|
48
|
+
## [v1.4.2](https://github.com/auth0/omniauth-auth0/tree/v1.4.2) (2016-06-13)
|
49
|
+
[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v1.4.2)
|
50
|
+
|
51
|
+
**Added**
|
52
|
+
- Link to OmniAuth site [\#36](https://github.com/auth0/omniauth-auth0/pull/36) ([jghaines](https://github.com/jghaines))
|
53
|
+
- add ssl fix to RoR example [\#31](https://github.com/auth0/omniauth-auth0/pull/31) ([Amialc](https://github.com/Amialc))
|
54
|
+
- Update LICENSE [\#17](https://github.com/auth0/omniauth-auth0/pull/17) ([aguerere](https://github.com/aguerere))
|
55
|
+
|
56
|
+
**Changed**
|
57
|
+
- Update lock to version 9 [\#34](https://github.com/auth0/omniauth-auth0/pull/34) ([Annyv2](https://github.com/Annyv2))
|
58
|
+
- Update Gemfile [\#22](https://github.com/auth0/omniauth-auth0/pull/22) ([Annyv2](https://github.com/Annyv2))
|
59
|
+
- Update lock [\#15](https://github.com/auth0/omniauth-auth0/pull/15) ([Annyv2](https://github.com/Annyv2))
|
60
|
+
|
61
|
+
**Fixed**
|
62
|
+
- Fix setup [\#38](https://github.com/auth0/omniauth-auth0/pull/38) ([deepak](https://github.com/deepak))
|
63
|
+
- Added missing instruction [\#30](https://github.com/auth0/omniauth-auth0/pull/30) ([Annyv2](https://github.com/Annyv2))
|
64
|
+
- Fixes undefined Auth0Lock issue [\#28](https://github.com/auth0/omniauth-auth0/pull/28) ([Annyv2](https://github.com/Annyv2))
|
65
|
+
- Update Readme [\#27](https://github.com/auth0/omniauth-auth0/pull/27) ([Annyv2](https://github.com/Annyv2))
|
66
|
+
|
67
|
+
|
20
68
|
## [v1.4.1](https://github.com/auth0/omniauth-auth0/tree/v1.4.1) (2015-11-18)
|
21
69
|
[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.0...v1.4.1)
|
22
70
|
|
data/CODE_OF_CONDUCT.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Contribution
|
2
|
+
|
3
|
+
**Thank you in advance for your contribution!**
|
4
|
+
|
5
|
+
Please read [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) before beginning work on your contribution here.
|
6
|
+
|
7
|
+
## Environment setup
|
8
|
+
|
9
|
+
The best way we've found to develop gems locally is by using a local setting for your Bundler config. First, checkout the project locally:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
$ pwd
|
13
|
+
/PROJECT_ROOT/
|
14
|
+
$ mkdir vendor # if one does not exist
|
15
|
+
$ echo "/vendor/" >> .gitignore
|
16
|
+
$ git clone git@github.com:auth0/omniauth-auth0.git vendor/omniauth-auth0
|
17
|
+
Cloning into 'vendor/omniauth-auth0'...
|
18
|
+
```
|
19
|
+
|
20
|
+
Now, run the following command in your project root directory:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ bundle config --local local.omniauth-auth0 /PROJECT_ROOT/vendor/omniauth-auth0
|
24
|
+
You are replacing the current local value of local.omniauth-auth0, which is currently nil
|
25
|
+
$ bundle config
|
26
|
+
Settings are listed in order of priority. The top value will be used.
|
27
|
+
local.omniauth-auth0
|
28
|
+
Set for your local app (/PROJECT_ROOT/.bundle/config): "/PROJECT_ROOT/vendor/omniauth-auth0"
|
29
|
+
```
|
30
|
+
|
31
|
+
Finally, add or change the gem include to add a `github:` param:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
source 'https://rubygems.org'
|
35
|
+
# ...
|
36
|
+
# OmniAuth strategy for authenticating with Auth0
|
37
|
+
gem 'omniauth-auth0', github: 'auth0/omniauth-auth0'
|
38
|
+
#..
|
39
|
+
```
|
40
|
+
|
41
|
+
Now you should be able to make changes locally and have them reflected in your test app. Keep in mind you'll need to restart your app between changes.
|
42
|
+
|
43
|
+
[Great explanation for why this setup works well](https://rossta.net/blog/how-to-specify-local-ruby-gems-in-your-gemfile.html).
|
44
|
+
|
45
|
+
## Testing
|
46
|
+
|
47
|
+
Tests should be added for additional or modified functionality and all tests should run successfully before submitting a PR.
|
48
|
+
|
49
|
+
### Adding tests
|
50
|
+
|
51
|
+
All new tests should be added to the `/spec/omniauth` directory. Testing resources, like JSON fixtures, should be added to the `/spec/resources` directory.
|
52
|
+
|
53
|
+
### Running tests
|
54
|
+
|
55
|
+
Running tests is as simple as:
|
56
|
+
|
57
|
+
```bash
|
58
|
+
$ bundle exec rake spec
|
59
|
+
```
|
60
|
+
|
61
|
+
## Documentation
|
62
|
+
|
63
|
+
Documentation for this gem is primarily done at the code level. All new methods should include a docblock at least.
|
64
|
+
|
65
|
+
## Code quality tools
|
66
|
+
|
67
|
+
Code quality is enforced across the entire gem with Rubocop:
|
68
|
+
|
69
|
+
```bash
|
70
|
+
$ bundle exec rake rubocop
|
71
|
+
```
|
data/Gemfile
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
source '
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem 'gem-release'
|
6
|
+
gem 'jwt'
|
6
7
|
gem 'rake'
|
7
8
|
|
8
9
|
group :development do
|
@@ -11,6 +12,7 @@ group :development do
|
|
11
12
|
gem 'shotgun'
|
12
13
|
gem 'sinatra'
|
13
14
|
gem 'thin'
|
15
|
+
gem 'rubocop', require: false
|
14
16
|
end
|
15
17
|
|
16
18
|
group :test do
|
@@ -18,9 +20,6 @@ group :test do
|
|
18
20
|
gem 'listen', '~> 3.1.5'
|
19
21
|
gem 'rack-test'
|
20
22
|
gem 'rspec', '~> 3.5'
|
21
|
-
gem 'rubocop', '>= 0.30', platforms: [
|
22
|
-
:ruby_19, :ruby_20, :ruby_21, :ruby_22
|
23
|
-
]
|
24
23
|
gem 'simplecov'
|
25
24
|
gem 'webmock'
|
26
25
|
end
|
data/README.md
CHANGED
@@ -1,54 +1,57 @@
|
|
1
|
-
[![Build Status](https://travis-ci.org/auth0/omniauth-auth0.svg)](https://travis-ci.org/auth0/omniauth-auth0)
|
2
|
-
|
3
1
|
# OmniAuth Auth0
|
4
2
|
|
5
|
-
|
3
|
+
An [OmniAuth](https://github.com/intridea/omniauth) strategy for authenticating with [Auth0](https://auth0.com). This strategy is based on the [OmniAuth OAuth2](https://github.com/omniauth/omniauth-oauth2) strategy.
|
6
4
|
|
7
|
-
|
5
|
+
[![Build Status](https://travis-ci.org/auth0/omniauth-auth0.svg)](https://travis-ci.org/auth0/omniauth-auth0)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/auth0.svg)](http://badge.fury.io/rb/auth0)
|
7
|
+
[![MIT licensed](https://img.shields.io/dub/l/vibe-d.svg?style=flat)](https://github.com/auth0/ruby-auth0/blob/master/LICENSE)
|
8
8
|
|
9
|
-
|
9
|
+
## Table of Contents
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
- [Documentation](#documentation)
|
12
|
+
- [Installation](#installation)
|
13
|
+
- [Getting Started](#getting-started)
|
14
|
+
- [Contribution](#contribution)
|
15
|
+
- [Support + Feedback](#support--feedback)
|
16
|
+
- [Vulnerability Reporting](#vulnerability-reporting)
|
17
|
+
- [What is Auth0](#what-is-auth0)
|
18
|
+
- [License](#license)
|
19
|
+
|
20
|
+
## Documentation
|
14
21
|
|
15
|
-
|
22
|
+
- [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails)
|
23
|
+
- [Sample projects](https://github.com/auth0-samples/auth0-rubyonrails-sample)
|
16
24
|
|
17
|
-
##
|
25
|
+
## Installation
|
18
26
|
|
19
|
-
|
27
|
+
Add the following line to your `Gemfile`:
|
20
28
|
|
21
29
|
```ruby
|
22
|
-
|
23
|
-
provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
|
24
|
-
end
|
30
|
+
gem 'omniauth-auth0'
|
25
31
|
```
|
26
32
|
|
27
|
-
Then
|
33
|
+
Then install:
|
28
34
|
|
29
|
-
```
|
30
|
-
|
35
|
+
```bash
|
36
|
+
$ bundle install
|
31
37
|
```
|
32
38
|
|
33
|
-
|
39
|
+
See our [contributing guide](CONTRIBUTING.md) for information on local installation for development.
|
34
40
|
|
35
|
-
|
36
|
-
use OmniAuth::Builder do
|
37
|
-
provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
|
38
|
-
end
|
39
|
-
```
|
41
|
+
## Getting Started
|
40
42
|
|
41
|
-
|
43
|
+
To start processing authentication requests, the following steps must be performed:
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
1. Initialize the strategy
|
46
|
+
2. Configure the callback controller
|
47
|
+
3. Add the required routes
|
48
|
+
4. Trigger an authentication request
|
46
49
|
|
47
|
-
|
50
|
+
All of these tasks and more are covered in our [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails).
|
48
51
|
|
49
|
-
###
|
52
|
+
### Additional authentication parameters
|
50
53
|
|
51
|
-
To send additional parameters during login you can specify them when you register the provider
|
54
|
+
To send additional parameters during login, you can specify them when you register the provider:
|
52
55
|
|
53
56
|
```ruby
|
54
57
|
provider
|
@@ -64,80 +67,89 @@ provider
|
|
64
67
|
}
|
65
68
|
```
|
66
69
|
|
67
|
-
|
70
|
+
... which will tell the strategy to send those parameters on every Auth request.
|
68
71
|
|
69
|
-
Or you can do it for a specific
|
72
|
+
Or you can do it for a specific authentication request by adding them to the query parameters of the redirect URL. Allowed parameters are `connection` and `prompt`:
|
70
73
|
|
71
74
|
```ruby
|
72
75
|
redirect_to '/auth/auth0?connection=google-oauth2'
|
76
|
+
redirect_to '/auth/auth0?prompt=none'
|
73
77
|
```
|
74
78
|
|
75
|
-
###
|
79
|
+
### Authentication hash
|
76
80
|
|
77
|
-
Auth0 strategy will
|
81
|
+
The Auth0 strategy will provide the standard OmniAuth hash attributes:
|
78
82
|
|
79
|
-
- provider
|
80
|
-
- uid
|
81
|
-
- info
|
82
|
-
- credentials
|
83
|
-
- extra
|
83
|
+
- `:provider` - the name of the strategy, in this case `auth0`
|
84
|
+
- `:uid` - the user identifier
|
85
|
+
- `:info` - the result of the call to `/userinfo` using OmniAuth standard attributes
|
86
|
+
- `:credentials` - tokens requested and data
|
87
|
+
- `:extra` - Additional info obtained from calling `/userinfo` in the `:raw_info` property
|
84
88
|
|
85
89
|
```ruby
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
90
|
+
{
|
91
|
+
:provider => 'auth0',
|
92
|
+
:uid => 'auth0|USER_ID',
|
93
|
+
:info => {
|
94
|
+
:name => 'John Foo',
|
95
|
+
:email => 'johnfoo@example.org',
|
96
|
+
:nickname => 'john',
|
97
|
+
:image => 'https://example.org/john.jpg'
|
98
|
+
},
|
99
|
+
:credentials => {
|
100
|
+
:token => 'ACCESS_TOKEN',
|
101
|
+
:expires_at => 1485373937,
|
102
|
+
:expires => true,
|
103
|
+
:refresh_token => 'REFRESH_TOKEN',
|
104
|
+
:id_token => 'JWT_ID_TOKEN',
|
105
|
+
:token_type => 'bearer',
|
106
|
+
},
|
107
|
+
:extra => {
|
108
|
+
:raw_info => {
|
109
|
+
:email => 'johnfoo@example.org',
|
110
|
+
:email_verified => 'true',
|
111
|
+
:name => 'John Foo',
|
112
|
+
:picture => 'https://example.org/john.jpg',
|
113
|
+
:user_id => 'auth0|USER_ID',
|
114
|
+
:nickname => 'john',
|
115
|
+
:created_at => '2014-07-15T17:19:50.387Z'
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
115
119
|
```
|
116
120
|
|
117
|
-
|
121
|
+
## Contribution
|
118
122
|
|
119
|
-
|
123
|
+
We appreciate feedback and contribution to this repo! Before you get started, please see the following:
|
120
124
|
|
121
|
-
|
125
|
+
- [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
|
126
|
+
- [Auth0's Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
|
127
|
+
- [This repo's contribution guide](CONTRIBUTING.md)
|
122
128
|
|
123
|
-
|
124
|
-
CrazyApp::Application.config.session_store :cache_store
|
129
|
+
## Support + Feedback
|
125
130
|
|
126
|
-
# /config/environments/development.rb
|
127
|
-
config.cache_store = :memory_store
|
128
131
|
|
129
|
-
|
132
|
+
- Use [Community](https://community.auth0.com/) for usage, questions, specific cases.
|
133
|
+
- Use [Issues](https://github.com/auth0/omniauth-auth0/issues) here for code-level support and bug reports.
|
134
|
+
- Paid customers can use [Support](https://support.auth0.com/) to submit a trouble ticket for production-affecting issues.
|
135
|
+
|
136
|
+
## Vulnerability Reporting
|
130
137
|
|
131
|
-
|
138
|
+
Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
|
132
139
|
|
133
|
-
##
|
140
|
+
## What is Auth0?
|
134
141
|
|
135
|
-
|
142
|
+
Auth0 helps you to easily:
|
136
143
|
|
137
|
-
|
144
|
+
- implement authentication with multiple identity providers, including social (e.g., Google, Facebook, Microsoft, LinkedIn, GitHub, Twitter, etc), or enterprise (e.g., Windows Azure AD, Google Apps, Active Directory, ADFS, SAML, etc.)
|
145
|
+
- log in users with username/password databases, passwordless, or multi-factor authentication
|
146
|
+
- link multiple user accounts together
|
147
|
+
- generate signed JSON Web Tokens to authorize your API calls and flow the user identity securely
|
148
|
+
- access demographics and analytics detailing how, when, and where users are logging in
|
149
|
+
- enrich user profiles from other data sources using customizable JavaScript rules
|
138
150
|
|
139
|
-
[Auth0](https://auth0.com)
|
151
|
+
[Why Auth0?](https://auth0.com/why-auth0)
|
140
152
|
|
141
153
|
## License
|
142
154
|
|
143
|
-
|
155
|
+
The OmniAuth Auth0 strategy is licensed under MIT - [LICENSE](LICENSE)
|
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ begin
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
rescue LoadError
|
12
12
|
task :rubocop do
|
13
|
-
|
13
|
+
warn 'Rubocop is disabled'
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -23,7 +23,7 @@ namespace :sinatra do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
desc 'Run specs'
|
26
|
-
task default: [
|
26
|
+
task default: %i[spec rubocop]
|
27
27
|
task test: :spec
|
28
28
|
task :guard do
|
29
29
|
system 'bundle exec guard'
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
require 'omniauth'
|
5
|
+
|
6
|
+
module OmniAuth
|
7
|
+
module Auth0
|
8
|
+
# JWT Validator class
|
9
|
+
class JWTValidator
|
10
|
+
attr_accessor :issuer
|
11
|
+
|
12
|
+
# Initializer
|
13
|
+
# @param options object
|
14
|
+
# options.domain - Application domain.
|
15
|
+
# options.client_id - Application Client ID.
|
16
|
+
# options.client_secret - Application Client Secret.
|
17
|
+
def initialize(options)
|
18
|
+
temp_domain = URI(options.domain)
|
19
|
+
temp_domain = URI("https://#{options.domain}") unless temp_domain.scheme
|
20
|
+
@issuer = "#{temp_domain}/"
|
21
|
+
|
22
|
+
@client_id = options.client_id
|
23
|
+
@client_secret = options.client_secret
|
24
|
+
end
|
25
|
+
|
26
|
+
# Decode a JWT.
|
27
|
+
# @param jwt string - JWT to decode.
|
28
|
+
# @return hash - The decoded token, if there were no exceptions.
|
29
|
+
# @see https://github.com/jwt/ruby-jwt
|
30
|
+
def decode(jwt)
|
31
|
+
head = token_head(jwt)
|
32
|
+
|
33
|
+
# Make sure the algorithm is supported and get the decode key.
|
34
|
+
decode_key = @client_secret
|
35
|
+
if head[:alg] == 'RS256'
|
36
|
+
decode_key = rs256_decode_key(head[:kid])
|
37
|
+
elsif head[:alg] != 'HS256'
|
38
|
+
raise JWT::VerificationError, :id_token_alg_unsupported
|
39
|
+
end
|
40
|
+
|
41
|
+
# Docs: https://github.com/jwt/ruby-jwt#algorithms-and-usage
|
42
|
+
JWT.decode(jwt, decode_key, true, decode_opts(head[:alg]))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the decoded head segment from a JWT.
|
46
|
+
# @return hash - The parsed head of the JWT passed, empty hash if not.
|
47
|
+
def token_head(jwt)
|
48
|
+
jwt_parts = jwt.split('.')
|
49
|
+
return {} if blank?(jwt_parts) || blank?(jwt_parts[0])
|
50
|
+
|
51
|
+
json_parse(Base64.decode64(jwt_parts[0]))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the JWKS from the issuer and return a public key.
|
55
|
+
# @param x5c string - X.509 certificate chain from a JWKS.
|
56
|
+
# @return key - The X.509 certificate public key.
|
57
|
+
def jwks_public_cert(x5c)
|
58
|
+
x5c = Base64.decode64(x5c)
|
59
|
+
|
60
|
+
# https://docs.ruby-lang.org/en/2.4.0/OpenSSL/X509/Certificate.html
|
61
|
+
OpenSSL::X509::Certificate.new(x5c).public_key
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return a specific key from a JWKS object.
|
65
|
+
# @param key string - Key to find in the JWKS.
|
66
|
+
# @param kid string - Key ID to identify the right JWK.
|
67
|
+
# @return nil|string
|
68
|
+
def jwks_key(key, kid)
|
69
|
+
return nil if blank?(jwks[:keys])
|
70
|
+
|
71
|
+
matching_jwk = jwks[:keys].find { |jwk| jwk[:kid] == kid }
|
72
|
+
matching_jwk[key] if matching_jwk
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Get the JWT decode options
|
78
|
+
# Docs: https://github.com/jwt/ruby-jwt#add-custom-header-fields
|
79
|
+
# @return hash
|
80
|
+
def decode_opts(alg)
|
81
|
+
{
|
82
|
+
algorithm: alg,
|
83
|
+
leeway: 30,
|
84
|
+
verify_expiration: true,
|
85
|
+
verify_iss: true,
|
86
|
+
iss: @issuer,
|
87
|
+
verify_aud: true,
|
88
|
+
aud: @client_id,
|
89
|
+
verify_not_before: true
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def rs256_decode_key(kid)
|
94
|
+
jwks_x5c = jwks_key(:x5c, kid)
|
95
|
+
raise JWT::VerificationError, :jwks_missing_x5c if jwks_x5c.nil?
|
96
|
+
|
97
|
+
jwks_public_cert(jwks_x5c.first)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get a JWKS from the issuer
|
101
|
+
# @return void
|
102
|
+
def jwks
|
103
|
+
jwks_uri = URI(@issuer + '.well-known/jwks.json')
|
104
|
+
@jwks ||= json_parse(Net::HTTP.get(jwks_uri))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Rails Active Support blank method.
|
108
|
+
# @param obj object - Object to check for blankness.
|
109
|
+
# @return boolean
|
110
|
+
def blank?(obj)
|
111
|
+
obj.respond_to?(:empty?) ? obj.empty? : !obj
|
112
|
+
end
|
113
|
+
|
114
|
+
# Parse JSON with symbolized names.
|
115
|
+
# @param json string - JSON to parse.
|
116
|
+
# @return hash
|
117
|
+
def json_parse(json)
|
118
|
+
JSON.parse(json, symbolize_names: true)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'uri'
|
3
3
|
require 'omniauth-oauth2'
|
4
|
+
require 'omniauth/auth0/jwt_validator'
|
4
5
|
|
5
6
|
module OmniAuth
|
6
7
|
module Strategies
|
@@ -8,12 +9,13 @@ module OmniAuth
|
|
8
9
|
class Auth0 < OmniAuth::Strategies::OAuth2
|
9
10
|
option :name, 'auth0'
|
10
11
|
|
11
|
-
args [
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
args %i[
|
13
|
+
client_id
|
14
|
+
client_secret
|
15
|
+
domain
|
15
16
|
]
|
16
17
|
|
18
|
+
# Setup client URLs used during authentication
|
17
19
|
def client
|
18
20
|
options.client_options.site = domain_url
|
19
21
|
options.client_options.authorize_url = '/authorize'
|
@@ -22,25 +24,42 @@ module OmniAuth
|
|
22
24
|
super
|
23
25
|
end
|
24
26
|
|
27
|
+
# Use the "sub" key of the userinfo returned
|
28
|
+
# as the uid (globally unique string identifier).
|
25
29
|
uid { raw_info['sub'] }
|
26
30
|
|
31
|
+
# Build the API credentials hash with returned auth data.
|
27
32
|
credentials do
|
28
|
-
|
29
|
-
|
33
|
+
credentials = {
|
34
|
+
'token' => access_token.token,
|
35
|
+
'expires' => true
|
36
|
+
}
|
37
|
+
|
30
38
|
if access_token.params
|
31
|
-
|
32
|
-
|
33
|
-
|
39
|
+
credentials.merge!(
|
40
|
+
'id_token' => access_token.params['id_token'],
|
41
|
+
'token_type' => access_token.params['token_type'],
|
42
|
+
'refresh_token' => access_token.refresh_token
|
43
|
+
)
|
34
44
|
end
|
35
|
-
|
45
|
+
|
46
|
+
# Make sure the ID token can be verified and decoded.
|
47
|
+
auth0_jwt = OmniAuth::Auth0::JWTValidator.new(options)
|
48
|
+
jwt_decoded = auth0_jwt.decode(credentials['id_token'])
|
49
|
+
fail!(:invalid_id_token) unless jwt_decoded.length
|
50
|
+
|
51
|
+
credentials
|
36
52
|
end
|
37
53
|
|
54
|
+
# Store all raw information for use in the session.
|
38
55
|
extra do
|
39
56
|
{
|
40
57
|
raw_info: raw_info
|
41
58
|
}
|
42
59
|
end
|
43
60
|
|
61
|
+
# Build a hash of information about the user
|
62
|
+
# with keys taken from the Auth Hash Schema.
|
44
63
|
info do
|
45
64
|
{
|
46
65
|
name: raw_info['name'] || raw_info['sub'],
|
@@ -50,49 +69,64 @@ module OmniAuth
|
|
50
69
|
}
|
51
70
|
end
|
52
71
|
|
72
|
+
# Define the parameters used for the /authorize endpoint
|
53
73
|
def authorize_params
|
54
74
|
params = super
|
55
75
|
params['auth0Client'] = client_info
|
76
|
+
parse_query = Rack::Utils.parse_query(request.query_string)
|
77
|
+
params['connection'] = parse_query['connection']
|
78
|
+
params['prompt'] = parse_query['prompt']
|
56
79
|
params
|
57
80
|
end
|
58
81
|
|
82
|
+
# Declarative override for the request phase of authentication
|
59
83
|
def request_phase
|
60
84
|
if no_client_id?
|
85
|
+
# Do we have a client_id for this Application?
|
61
86
|
fail!(:missing_client_id)
|
62
87
|
elsif no_client_secret?
|
88
|
+
# Do we have a client_secret for this Application?
|
63
89
|
fail!(:missing_client_secret)
|
64
90
|
elsif no_domain?
|
91
|
+
# Do we have a domain for this Application?
|
65
92
|
fail!(:missing_domain)
|
66
93
|
else
|
94
|
+
# All checks pass, run the Oauth2 request_phase method.
|
67
95
|
super
|
68
96
|
end
|
69
97
|
end
|
70
98
|
|
71
99
|
private
|
72
100
|
|
101
|
+
# Parse the raw user info.
|
73
102
|
def raw_info
|
74
103
|
userinfo_url = options.client_options.userinfo_url
|
75
104
|
@raw_info ||= access_token.get(userinfo_url).parsed
|
76
105
|
end
|
77
106
|
|
107
|
+
# Check if the options include a client_id
|
78
108
|
def no_client_id?
|
79
109
|
['', nil].include?(options.client_id)
|
80
110
|
end
|
81
111
|
|
112
|
+
# Check if the options include a client_secret
|
82
113
|
def no_client_secret?
|
83
114
|
['', nil].include?(options.client_secret)
|
84
115
|
end
|
85
116
|
|
117
|
+
# Check if the options include a domain
|
86
118
|
def no_domain?
|
87
119
|
['', nil].include?(options.domain)
|
88
120
|
end
|
89
121
|
|
122
|
+
# Normalize a domain to a URL.
|
90
123
|
def domain_url
|
91
124
|
domain_url = URI(options.domain)
|
92
125
|
domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
|
93
126
|
domain_url.to_s
|
94
127
|
end
|
95
128
|
|
129
|
+
# Build the auth0Client URL parameter for metrics.
|
96
130
|
def client_info
|
97
131
|
client_info = JSON.dump(
|
98
132
|
name: 'omniauth-auth0',
|
data/omniauth-auth0.gemspec
CHANGED
@@ -8,12 +8,12 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ['Auth0']
|
9
9
|
s.email = ['info@auth0.com']
|
10
10
|
s.homepage = 'https://github.com/auth0/omniauth-auth0'
|
11
|
-
s.summary = '
|
11
|
+
s.summary = 'OmniAuth OAuth2 strategy for the Auth0 platform.'
|
12
12
|
s.description = %q{Auth0 is an authentication broker that supports social identity providers as well as enterprise identity providers such as Active Directory, LDAP, Google Apps, Salesforce.
|
13
13
|
|
14
14
|
OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible.
|
15
15
|
|
16
|
-
omniauth-auth0 is the
|
16
|
+
omniauth-auth0 is the OmniAuth strategy for Auth0.
|
17
17
|
}
|
18
18
|
|
19
19
|
s.rubyforge_project = 'omniauth-auth0'
|
@@ -23,7 +23,7 @@ omniauth-auth0 is the omniauth strategy for Auth0.
|
|
23
23
|
s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
|
24
24
|
s.require_paths = ['lib']
|
25
25
|
|
26
|
-
s.add_runtime_dependency 'omniauth-oauth2', '~> 1.
|
26
|
+
s.add_runtime_dependency 'omniauth-oauth2', '~> 1.5'
|
27
27
|
|
28
28
|
s.add_development_dependency 'bundler', '~> 1.9'
|
29
29
|
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
require 'jwt'
|
4
|
+
|
5
|
+
describe OmniAuth::Auth0::JWTValidator do
|
6
|
+
#
|
7
|
+
# Reused data
|
8
|
+
#
|
9
|
+
|
10
|
+
let(:client_id) { 'CLIENT_ID' }
|
11
|
+
let(:client_secret) { 'CLIENT_SECRET' }
|
12
|
+
let(:domain) { 'samples.auth0.com' }
|
13
|
+
let(:future_timecode) { 32_503_680_000 }
|
14
|
+
let(:past_timecode) { 303_912_000 }
|
15
|
+
let(:jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
|
16
|
+
|
17
|
+
let(:rsa_private_key) do
|
18
|
+
OpenSSL::PKey::RSA.generate 2048
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:rsa_token_jwks) do
|
22
|
+
{
|
23
|
+
keys: [
|
24
|
+
{
|
25
|
+
kid: jwks_kid,
|
26
|
+
x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)]
|
27
|
+
}
|
28
|
+
]
|
29
|
+
}.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:jwks) do
|
33
|
+
current_dir = File.dirname(__FILE__)
|
34
|
+
jwks_file = File.read("#{current_dir}/../../resources/jwks.json")
|
35
|
+
JSON.parse(jwks_file, symbolize_names: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
Options = Struct.new(:domain, :client_id, :client_secret)
|
39
|
+
|
40
|
+
#
|
41
|
+
# Specs
|
42
|
+
#
|
43
|
+
|
44
|
+
describe 'JWT verifier default values' do
|
45
|
+
let(:jwt_validator) do
|
46
|
+
make_jwt_validator
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should have the correct issuer' do
|
50
|
+
expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'JWT verifier token_head' do
|
55
|
+
let(:jwt_validator) do
|
56
|
+
make_jwt_validator
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should parse the head of a valid JWT' do
|
60
|
+
expect(jwt_validator.token_head(make_hs256_token)[:alg]).to eq('HS256')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should fail parsing the head of a blank JWT' do
|
64
|
+
expect(jwt_validator.token_head('')).to eq({})
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should fail parsing the head of an invalid JWT' do
|
68
|
+
expect(jwt_validator.token_head('.')).to eq({})
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should throw an exception for invalid JSON' do
|
72
|
+
expect do
|
73
|
+
jwt_validator.token_head('QXV0aDA=')
|
74
|
+
end.to raise_error(JSON::ParserError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'JWT verifier jwks_public_cert' do
|
79
|
+
let(:jwt_validator) do
|
80
|
+
make_jwt_validator
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should return a public_key' do
|
84
|
+
x5c = jwks[:keys].first[:x5c].first
|
85
|
+
public_cert = jwt_validator.jwks_public_cert(x5c)
|
86
|
+
expect(public_cert.instance_of?(OpenSSL::PKey::RSA)).to eq(true)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should fail with an invalid x5c' do
|
90
|
+
expect do
|
91
|
+
jwt_validator.jwks_public_cert('QXV0aDA=')
|
92
|
+
end.to raise_error(OpenSSL::X509::CertificateError)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'JWT verifier jwks_key' do
|
97
|
+
let(:jwt_validator) do
|
98
|
+
make_jwt_validator
|
99
|
+
end
|
100
|
+
|
101
|
+
before do
|
102
|
+
stub_jwks
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return a key' do
|
106
|
+
expect(jwt_validator.jwks_key(:alg, jwks_kid)).to eq('RS256')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should return an x5c key' do
|
110
|
+
expect(jwt_validator.jwks_key(:x5c, jwks_kid).length).to eq(1)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should return nil if there is not key' do
|
114
|
+
expect(jwt_validator.jwks_key(:auth0, jwks_kid)).to eq(nil)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should return nil if the key ID is invalid' do
|
118
|
+
expect(jwt_validator.jwks_key(:alg, "#{jwks_kid}_invalid")).to eq(nil)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'JWT verifier decode' do
|
123
|
+
let(:jwt_validator) do
|
124
|
+
make_jwt_validator
|
125
|
+
end
|
126
|
+
|
127
|
+
before do
|
128
|
+
stub_jwks
|
129
|
+
stub_dummy_jwks
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should fail with passed expiration' do
|
133
|
+
payload = {
|
134
|
+
exp: past_timecode
|
135
|
+
}
|
136
|
+
token = make_hs256_token(payload)
|
137
|
+
expect do
|
138
|
+
jwt_validator.decode(token)
|
139
|
+
end.to raise_error(JWT::ExpiredSignature)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should fail with missing issuer' do
|
143
|
+
expect do
|
144
|
+
jwt_validator.decode(make_hs256_token)
|
145
|
+
end.to raise_error(JWT::InvalidIssuerError)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should fail with invalid issuer' do
|
149
|
+
payload = {
|
150
|
+
iss: 'https://auth0.com/'
|
151
|
+
}
|
152
|
+
token = make_hs256_token(payload)
|
153
|
+
expect do
|
154
|
+
jwt_validator.decode(token)
|
155
|
+
end.to raise_error(JWT::InvalidIssuerError)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should fail with a future not before' do
|
159
|
+
payload = {
|
160
|
+
nbf: future_timecode,
|
161
|
+
iss: "https://#{domain}/"
|
162
|
+
}
|
163
|
+
token = make_hs256_token(payload)
|
164
|
+
expect do
|
165
|
+
jwt_validator.decode(token)
|
166
|
+
end.to raise_error(JWT::ImmatureSignature)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should fail with missing audience' do
|
170
|
+
payload = {
|
171
|
+
iss: "https://#{domain}/"
|
172
|
+
}
|
173
|
+
token = make_hs256_token(payload)
|
174
|
+
expect do
|
175
|
+
jwt_validator.decode(token)
|
176
|
+
end.to raise_error(JWT::InvalidAudError)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should fail with invalid audience' do
|
180
|
+
payload = {
|
181
|
+
iss: "https://#{domain}/",
|
182
|
+
aud: 'Auth0'
|
183
|
+
}
|
184
|
+
token = make_hs256_token(payload)
|
185
|
+
expect do
|
186
|
+
jwt_validator.decode(token)
|
187
|
+
end.to raise_error(JWT::InvalidAudError)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should decode a valid HS256 token with multiple audiences' do
|
191
|
+
payload = {
|
192
|
+
iss: "https://#{domain}/",
|
193
|
+
aud: [
|
194
|
+
client_id,
|
195
|
+
"https://#{domain}/userinfo"
|
196
|
+
]
|
197
|
+
}
|
198
|
+
token = make_hs256_token(payload)
|
199
|
+
expect(jwt_validator.decode(token).length).to eq(2)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should decode a standard HS256 token' do
|
203
|
+
sub = 'abc123'
|
204
|
+
payload = {
|
205
|
+
sub: sub,
|
206
|
+
exp: future_timecode,
|
207
|
+
iss: "https://#{domain}/",
|
208
|
+
iat: past_timecode,
|
209
|
+
aud: client_id
|
210
|
+
}
|
211
|
+
token = make_hs256_token(payload)
|
212
|
+
decoded_token = jwt_validator.decode(token)
|
213
|
+
expect(decoded_token.first['sub']).to eq(sub)
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should decode a standard RS256 token' do
|
217
|
+
domain = 'example.org'
|
218
|
+
sub = 'abc123'
|
219
|
+
payload = {
|
220
|
+
sub: sub,
|
221
|
+
exp: future_timecode,
|
222
|
+
iss: "https://#{domain}/",
|
223
|
+
iat: past_timecode,
|
224
|
+
aud: client_id,
|
225
|
+
kid: jwks_kid
|
226
|
+
}
|
227
|
+
token = make_rs256_token(payload)
|
228
|
+
decoded_token = make_jwt_validator(domain).decode(token)
|
229
|
+
expect(decoded_token.first['sub']).to eq(sub)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def make_jwt_validator(opt_domain = domain)
|
236
|
+
OmniAuth::Auth0::JWTValidator.new(
|
237
|
+
Options.new(
|
238
|
+
opt_domain,
|
239
|
+
client_id,
|
240
|
+
client_secret
|
241
|
+
)
|
242
|
+
)
|
243
|
+
end
|
244
|
+
|
245
|
+
def make_hs256_token(payload = nil)
|
246
|
+
payload = { sub: 'abc123' } if payload.nil?
|
247
|
+
JWT.encode payload, client_secret, 'HS256'
|
248
|
+
end
|
249
|
+
|
250
|
+
def make_rs256_token(payload = nil)
|
251
|
+
payload = { sub: 'abc123' } if payload.nil?
|
252
|
+
JWT.encode payload, rsa_private_key, 'RS256', kid: jwks_kid
|
253
|
+
end
|
254
|
+
|
255
|
+
def make_cert(private_key)
|
256
|
+
cert = OpenSSL::X509::Certificate.new
|
257
|
+
cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0')
|
258
|
+
cert.subject = cert.issuer
|
259
|
+
cert.not_before = Time.now
|
260
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
261
|
+
cert.public_key = private_key.public_key
|
262
|
+
cert.serial = 0x0
|
263
|
+
cert.version = 2
|
264
|
+
|
265
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
266
|
+
ef.subject_certificate = cert
|
267
|
+
ef.issuer_certificate = cert
|
268
|
+
cert.extensions = [
|
269
|
+
ef.create_extension('basicConstraints', 'CA:TRUE', true),
|
270
|
+
ef.create_extension('subjectKeyIdentifier', 'hash')
|
271
|
+
]
|
272
|
+
cert.add_extension ef.create_extension(
|
273
|
+
'authorityKeyIdentifier',
|
274
|
+
'keyid:always,issuer:always'
|
275
|
+
)
|
276
|
+
|
277
|
+
cert.sign private_key, OpenSSL::Digest::SHA1.new
|
278
|
+
end
|
279
|
+
|
280
|
+
def stub_jwks
|
281
|
+
stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json')
|
282
|
+
.to_return(
|
283
|
+
headers: { 'Content-Type' => 'application/json' },
|
284
|
+
body: jwks.to_json,
|
285
|
+
status: 200
|
286
|
+
)
|
287
|
+
end
|
288
|
+
|
289
|
+
def stub_bad_jwks
|
290
|
+
stub_request(:get, 'https://samples.auth0.com/.well-known/jwks-bad.json')
|
291
|
+
.to_return(
|
292
|
+
status: 404
|
293
|
+
)
|
294
|
+
end
|
295
|
+
|
296
|
+
def stub_dummy_jwks
|
297
|
+
stub_request(:get, 'https://example.org/.well-known/jwks.json')
|
298
|
+
.to_return(
|
299
|
+
headers: { 'Content-Type' => 'application/json' },
|
300
|
+
body: rsa_token_jwks,
|
301
|
+
status: 200
|
302
|
+
)
|
303
|
+
end
|
304
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'jwt'
|
2
3
|
|
3
4
|
RSpec.shared_examples 'site has valid domain url' do |url|
|
4
5
|
it { expect(subject.site).to eq(url) }
|
@@ -80,12 +81,23 @@ describe OmniAuth::Strategies::Auth0 do
|
|
80
81
|
expect(redirect_url).to have_query('redirect_uri')
|
81
82
|
end
|
82
83
|
|
84
|
+
it 'redirects to hosted login page' do
|
85
|
+
get 'auth/auth0?connection=abcd'
|
86
|
+
expect(last_response.status).to eq(302)
|
87
|
+
redirect_url = last_response.headers['Location']
|
88
|
+
expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
|
89
|
+
expect(redirect_url).to have_query('response_type', 'code')
|
90
|
+
expect(redirect_url).to have_query('state')
|
91
|
+
expect(redirect_url).to have_query('client_id')
|
92
|
+
expect(redirect_url).to have_query('redirect_uri')
|
93
|
+
expect(redirect_url).to have_query('connection', 'abcd')
|
94
|
+
end
|
95
|
+
|
83
96
|
describe 'callback' do
|
84
97
|
let(:access_token) { 'access token' }
|
85
98
|
let(:expires_in) { 2000 }
|
86
99
|
let(:token_type) { 'bearer' }
|
87
100
|
let(:refresh_token) { 'refresh token' }
|
88
|
-
let(:id_token) { 'id token' }
|
89
101
|
|
90
102
|
let(:user_id) { 'user identifier' }
|
91
103
|
let(:state) { SecureRandom.hex(8) }
|
@@ -95,8 +107,17 @@ describe OmniAuth::Strategies::Auth0 do
|
|
95
107
|
let(:email) { 'mail@mail.com' }
|
96
108
|
let(:email_verified) { true }
|
97
109
|
|
110
|
+
let(:id_token) do
|
111
|
+
payload = {}
|
112
|
+
payload['sub'] = user_id
|
113
|
+
payload['iss'] = "#{domain_url}/"
|
114
|
+
payload['aud'] = client_id
|
115
|
+
JWT.encode payload, client_secret, 'HS256'
|
116
|
+
end
|
117
|
+
|
98
118
|
let(:oauth_response) do
|
99
119
|
{
|
120
|
+
id_token: id_token,
|
100
121
|
access_token: access_token,
|
101
122
|
expires_in: expires_in,
|
102
123
|
token_type: token_type
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"keys": [
|
3
|
+
{
|
4
|
+
"alg": "RS256",
|
5
|
+
"kty": "RSA",
|
6
|
+
"use": "sig",
|
7
|
+
"x5c": [
|
8
|
+
"MIIDCzCCAfOgAwIBAgIJAJP6qydiMpsuMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNVBAMMEXNhbXBsZXMuYXV0aDAuY29tMB4XDTE0MDUyNjIyMDA1MFoXDTI4MDIwMjIyMDA1MFowHDEaMBgGA1UEAwwRc2FtcGxlcy5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkH4CFGSJ4s3mwCBzaGGwxa9Jxzfb1ia4nUumxbsuaB7PClZZgrNQiOR3MXVNV9W6F1D+wjT6oFHOo7TOkVI22I/ff3XZTE0F35UUHGWRtiQ4LdZxwOPTed2Lax3F2DEyl3Y0CguUKbq2sSghvHYcggM6aj3N53VBsnBh/kdrURDLx1RYqBIL6Fvkhb/V/v/u9UKhZM0CDQRef9FZ7R8q9ie9cnbDOj1dT9d64kiJIYtTraG0gOrs4LI+4KK0EZu5R7Uo053IK7kfNasWhDkl8yxNYkDxwfcIuAcDmLgLnAI4tfW5beJuw+/w75PO/EwzwsnvppXaAz7e3Wf8g1yWFAgMBAAGjUDBOMB0GA1UdDgQWBBTsmytFLNox+NUZdTNlCUL3hHrngTAfBgNVHSMEGDAWgBTsmytFLNox+NUZdTNlCUL3hHrngTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAodbRX/34LnWB70l8dpDF1neDoG29F0XdpE9ICWHeWB1gb/FvJ5UMy9/pnL0DI3mPwkTDDob+16Zc68o6dT6sH3vEUP1iRreJlFADEmJZjrH9P4Y7ttx3G2Uw2RU5uucXIqiyMDBrQo4vx4Lnghl+b/WYbZJgzLfZLgkOEjcznS0Yi5Wdz6MvaL3FehSfweHyrjmxz0e8elHq7VY8OqRA+4PmUBce9BgDCk9fZFjgj8l0m9Vc5pPKSY9LMmTyrYkeDr/KppqdXKOCHmv7AIGb6rMCtbkIL/CM7Bh9Hx78/UKAz87Sl9A1yXVNjKbZwOEW60ORIwJmd8Tv46gJF+/rV"
|
9
|
+
],
|
10
|
+
"n": "pB-AhRkieLN5sAgc2hhsMWvScc329YmuJ1LpsW7LmgezwpWWYKzUIjkdzF1TVfVuhdQ_sI0-qBRzqO0zpFSNtiP33912UxNBd-VFBxlkbYkOC3WccDj03ndi2sdxdgxMpd2NAoLlCm6trEoIbx2HIIDOmo9zed1QbJwYf5Ha1EQy8dUWKgSC-hb5IW_1f7_7vVCoWTNAg0EXn_RWe0fKvYnvXJ2wzo9XU_XeuJIiSGLU62htIDq7OCyPuCitBGbuUe1KNOdyCu5HzWrFoQ5JfMsTWJA8cH3CLgHA5i4C5wCOLX1uW3ibsPv8O-TzvxMM8LJ76aV2gM-3t1n_INclhQ",
|
11
|
+
"e": "AQAB",
|
12
|
+
"kid": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg",
|
13
|
+
"x5t": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg"
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"alg": "RS256",
|
17
|
+
"kty": "RSA",
|
18
|
+
"use": "sig",
|
19
|
+
"x5c": [
|
20
|
+
"MIIC8DCCAdigAwIBAgIJ4pL5sRgcIYGZMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNVBAMTFGxiYWxtYWNlZGEuYXV0aDAuY29tMB4XDTE1MTIxMjE5MDczM1oXDTI5MDgyMDE5MDczM1owHzEdMBsGA1UEAxMUbGJhbG1hY2VkYS5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPoo5DA/X8suAZujdmD2D88Ggtu8G/kuLUdEuj1W3+wzmFcEqQpE532rg8L0uppWKAbmLWzkuwyioNDhWwCtXnug3BFQf5Lrc6nTxjk4ZQt/HdsYWCGSSZueMUG/3I+2PSql3atD2nedjY6Z9hWU8kzOjF9wzkLMgPf/OYpuz9A+6d+/K8jApRPfsQ1LDVWDG8YRtj+IyHhSvXS+cK03iuD7yVLKkIZuoS8ymMJpnZONHGds/3P9pHY29KqliSYW0eGEX3BIarZG06gRJ+88WUbRi9+rfVAoGLq++S+bc021txK+qYS3nknhY0uv/ODBb4eeycuDjjdyLBCShVvbXFAgMBAAGjLzAtMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFG38TTjyzhRmpK7MXfvBXDcBtYJ3MA0GCSqGSIb3DQEBBQUAA4IBAQCLNW+rA25tjHs6Sa9VPgBfMMLd1PIEgMpQhET9JqpGYUgB+0q1leXw1cwh14x/6PF2oo3jPOMW+wCDA7KAVKYewYSr/Enph+zNFPaq2YQL9dCsVFcBsnEGznwZaqHrqxQDX9S2Ek6E9jNsuBCSpAPcTsfbn2TXz77V+HZ/4tbwRvYEX1S5agiZFyjZzJMiZU1KQzP5PhfzD6RPl5KTK2PYRhVdXwyuFxOdJzCzOC9E/Uw30Zd6+9oHmoNfvJr8BRy67YWjXaQAh2m8e+zv/dEzPimgvaLmI1yz4W+93dJy3NdMuCvObOqA534tviv5PkV57ewXAnWPbxyBHr57HdQ1"
|
21
|
+
],
|
22
|
+
"n": "z6KOQwP1_LLgGbo3Zg9g_PBoLbvBv5Li1HRLo9Vt_sM5hXBKkKROd9q4PC9LqaVigG5i1s5LsMoqDQ4VsArV57oNwRUH-S63Op08Y5OGULfx3bGFghkkmbnjFBv9yPtj0qpd2rQ9p3nY2OmfYVlPJMzoxfcM5CzID3_zmKbs_QPunfvyvIwKUT37ENSw1VgxvGEbY_iMh4Ur10vnCtN4rg-8lSypCGbqEvMpjCaZ2TjRxnbP9z_aR2NvSqpYkmFtHhhF9wSGq2RtOoESfvPFlG0Yvfq31QKBi6vvkvm3NNtbcSvqmEt55J4WNLr_zgwW-HnsnLg443ciwQkoVb21xQ",
|
23
|
+
"e": "AQAB",
|
24
|
+
"kid": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg",
|
25
|
+
"x5t": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg"
|
26
|
+
}
|
27
|
+
]
|
28
|
+
}
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-auth0
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Auth0
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: omniauth-oauth2
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,7 +43,7 @@ description: |
|
|
43
43
|
|
44
44
|
OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible.
|
45
45
|
|
46
|
-
omniauth-auth0 is the
|
46
|
+
omniauth-auth0 is the OmniAuth strategy for Auth0.
|
47
47
|
email:
|
48
48
|
- info@auth0.com
|
49
49
|
executables: []
|
@@ -51,11 +51,15 @@ extensions: []
|
|
51
51
|
extra_rdoc_files: []
|
52
52
|
files:
|
53
53
|
- ".gemrelease"
|
54
|
+
- ".github/ISSUE_TEMPLATE.md"
|
55
|
+
- ".github/PULL_REQUEST_TEMPLATE.md"
|
54
56
|
- ".gitignore"
|
55
57
|
- ".rspec"
|
56
58
|
- ".rubocop.yml"
|
57
59
|
- ".travis.yml"
|
58
60
|
- CHANGELOG.md
|
61
|
+
- CODE_OF_CONDUCT.md
|
62
|
+
- CONTRIBUTING.md
|
59
63
|
- Gemfile
|
60
64
|
- Guardfile
|
61
65
|
- LICENSE
|
@@ -65,9 +69,12 @@ files:
|
|
65
69
|
- examples/sinatra/config.ru
|
66
70
|
- lib/omniauth-auth0.rb
|
67
71
|
- lib/omniauth-auth0/version.rb
|
72
|
+
- lib/omniauth/auth0/jwt_validator.rb
|
68
73
|
- lib/omniauth/strategies/auth0.rb
|
69
74
|
- omniauth-auth0.gemspec
|
75
|
+
- spec/omniauth/auth0/jwt_validator_spec.rb
|
70
76
|
- spec/omniauth/strategies/auth0_spec.rb
|
77
|
+
- spec/resources/jwks.json
|
71
78
|
- spec/spec_helper.rb
|
72
79
|
homepage: https://github.com/auth0/omniauth-auth0
|
73
80
|
licenses:
|
@@ -89,10 +96,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
96
|
version: '0'
|
90
97
|
requirements: []
|
91
98
|
rubyforge_project: omniauth-auth0
|
92
|
-
rubygems_version: 2.
|
99
|
+
rubygems_version: 2.7.7
|
93
100
|
signing_key:
|
94
101
|
specification_version: 4
|
95
|
-
summary:
|
102
|
+
summary: OmniAuth OAuth2 strategy for the Auth0 platform.
|
96
103
|
test_files:
|
104
|
+
- spec/omniauth/auth0/jwt_validator_spec.rb
|
97
105
|
- spec/omniauth/strategies/auth0_spec.rb
|
106
|
+
- spec/resources/jwks.json
|
98
107
|
- spec/spec_helper.rb
|