Codebase list ruby-omniauth-auth0 / 7b7a3d6
Import upstream version 2.6.0, md5 41676aee53b4e0fd056a545a0dc04b7c Debian Janitor 3 years ago
29 changed file(s) with 2028 addition(s) and 194 deletion(s). Raw diff Collapse all Expand all
0 version: 2.1
1 matrix_rubyversions: &matrix_rubyversions
2 matrix:
3 parameters:
4 rubyversion: ["2.5", "2.6", "2.7", "3.0"]
5 # Default version of ruby to use for lint and publishing
6 default_rubyversion: &default_rubyversion "2.7"
7
8 executors:
9 ruby:
10 parameters:
11 rubyversion:
12 type: string
13 default: *default_rubyversion
14 docker:
15 - image: circleci/ruby:<< parameters.rubyversion >>
16
17 jobs:
18 run-tests:
19 parameters:
20 rubyversion:
21 type: string
22 default: *default_rubyversion
23 executor:
24 name: ruby
25 rubyversion: "<< parameters.rubyversion >>"
26 steps:
27 - checkout
28 - restore_cache:
29 keys:
30 - gems-v2-{{ checksum "Gemfile" }}
31 - gems-v2-
32 - run: bundle check || bundle install
33 - save_cache:
34 key: gems-v2--{{ checksum "Gemfile" }}
35 paths:
36 - vendor/bundle
37 - run: bundle exec rake spec
38
39 workflows:
40 tests:
41 jobs:
42 - run-tests:
43 <<: *matrix_rubyversions
0 * @auth0/dx-sdks-engineer
0 blank_issues_enabled: false
1 contact_links:
2 - name: Auth0 Community
3 url: https://community.auth0.com/c/sdks/5
4 about: Discuss this SDK in the Auth0 Community forums
5 - name: Library Documentation
6 url: https://github.com/auth0/omniauth-auth0#documentation
7 about: Read the library docs on Auth0.com
0 ---
1 name: Feature request
2 about: Suggest an idea or a feature for this project
3 title: ''
4 labels: feature request
5 assignees: ''
6 ---
7
8 <!--
9 **Please do not report security vulnerabilities here**. The Responsible Disclosure Program (https://auth0.com/whitehat) details the procedure for disclosing security issues.
10
11 Thank you in advance for helping us to improve this library! Your attention to detail here is greatly appreciated and will help us respond as quickly as possible. For general support or usage questions, use the Auth0 Community (https://community.auth0.com/) or Auth0 Support (https://support.auth0.com/). Finally, to avoid duplicates, please search existing Issues before submitting one here.
12
13 By submitting an Issue to this repository, you agree to the terms within the Auth0 Code of Conduct (https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
14 -->
15
16 ### Describe the problem you'd like to have solved
17
18 <!--
19 > A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
20 -->
21
22 ### Describe the ideal solution
23
24 <!--
25 > A clear and concise description of what you want to happen.
26 -->
27
28 ## Alternatives and current work-arounds
29
30 <!--
31 > A clear and concise description of any alternatives you've considered or any work-arounds that are currently in place.
32 -->
33
34 ### Additional information, if any
35
36 <!--
37 > Add any other context or screenshots about the feature request here.
38 -->
0 ---
1 name: Report a bug
2 about: Have you found a bug or issue? Create a bug report for this SDK
3 title: ''
4 labels: bug report
5 assignees: ''
6 ---
7
8 <!--
9 **Please do not report security vulnerabilities here**. The Responsible Disclosure Program (https://auth0.com/whitehat) details the procedure for disclosing security issues.
10
11 Thank you in advance for helping us to improve this library! 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. For general support or usage questions, use the Auth0 Community (https://community.auth0.com/) or Auth0 Support (https://support.auth0.com/). Finally, to avoid duplicates, please search existing Issues before submitting one here.
12
13 By submitting an Issue to this repository, you agree to the terms within the Auth0 Code of Conduct (https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
14 -->
15
16 ### Describe the problem
17
18 <!--
19 > Provide a clear and concise description of the issue
20 -->
21
22 ### What was the expected behavior?
23
24 <!--
25 > Tell us about the behavior you expected to see
26 -->
27
28 ### Reproduction
29 <!--
30 > Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent.
31 > **Note**: If clear, reproducable steps or the smallest sample app demonstrating misbehavior cannot be provided, we may not be able to follow up on this bug report.
32
33 > Where possible, please include:
34 >
35 > - The smallest possible sample app that reproduces the undesirable behavior
36 > - Log files (redact/remove sensitive information)
37 > - Application settings (redact/remove sensitive information)
38 > - Screenshots
39 -->
40
41 - Step 1..
42 - Step 2..
43 - ...
44
45 ### Environment
46
47 <!--
48 > Please provide the following:
49 -->
50
51 - **Version of this library used:**
52 - **Which framework are you using, if applicable:**
53 - **Other modules/plugins/libraries that might be involved:**
54 - **Any other relevant information you think would be useful:**
0 ### Changes
1
2 Please describe both what is changing and why this is important. Include:
3
4 - Endpoints added, deleted, deprecated, or changed
5 - Classes and methods added, deleted, deprecated, or changed
6 - Screenshots of new or changed UI, if applicable
7 - 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)
8
9 ### References
10
11 Please include relevant links supporting this change such as a:
12
13 - support ticket
14 - community post
15 - StackOverflow post
16 - support forum thread
17 - related GitHub issue in this or another repo
18
19 ### Testing
20
21 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.
22
23 * [ ] This change adds unit test coverage
24 * [ ] This change has been tested on the latest version of the platform/language or why not
25
26 ### Checklist
27
28 * [ ] I have read the [Auth0 contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
29 * [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
30 * [ ] All existing and new tests complete without errors
31 * [ ] All code quality tools/guidelines in the [CONTRIBUTING documentation](https://github.com/auth0/omniauth-auth0/blob/master/CONTRIBUTING.md) have been run/followed
0 # Configuration for probot-stale - https://github.com/probot/stale
1
2 # Number of days of inactivity before an Issue or Pull Request becomes stale
3 daysUntilStale: 90
4
5 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
6 daysUntilClose: 7
7
8 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
9 exemptLabels: []
10
11 # Set to true to ignore issues with an assignee (defaults to false)
12 exemptAssignees: true
13
14 # Label to use when marking as stale
15 staleLabel: closed:stale
16
17 # Comment to post when marking as stale. Set to `false` to disable
18 markComment: >
19 This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️
00 .ruby-version
11 coverage
2 Gemfile.lock
32 *.gem
43
54 .#*
65 .env
76 log/
8 tmp/
7 tmp/
8
9 ## Environment normalization:
10 /.bundle
11 /vendor/bundle
12
13 Gemfile.lock
0 # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
1 version: v1.13.5
2 # ignores vulnerabilities until expiry date; change duration by modifying expiry date
3 ignore:
4 SNYK-RUBY-OMNIAUTH-174820:
5 - '*':
6 reason: Not affected.
7 expires: 2020-01-01T00:00:00.000Z
8 patch: {}
+0
-6
.travis.yml less more
0 language: ruby
1 rvm:
2 - 2.2.5
3 branches:
4 only:
5 - master
00 # Change Log
1
2 ## [v2.6.0](https://github.com/auth0/omniauth-auth0/tree/v2.6.0) (2021-04-01)
3
4 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.5.0...v2.6.0)
5
6 **Added**
7 - Org Support [SDK-2395] [\#124](https://github.com/auth0/omniauth-auth0/pull/124) ([davidpatrick](https://github.com/davidpatrick))
8 - Add login_hint to permitted params [\#123](https://github.com/auth0/omniauth-auth0/pull/123) ([Roriz](https://github.com/Roriz))
9
10 ## [v2.5.0](https://github.com/auth0/omniauth-auth0/tree/v2.5.0) (2021-01-21)
11
12 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.2...v2.5.0)
13
14 **Added**
15 - Parsing claims from the id_token [\#120](https://github.com/auth0/omniauth-auth0/pull/120) ([davidpatrick](https://github.com/davidpatrick))
16
17 **Changed**
18 - Setup build matrix in CI [\#116](https://github.com/auth0/omniauth-auth0/pull/116) ([dmathieu](https://github.com/dmathieu))
19
20 **Fixed**
21 - Fixes params passed to authorize [\#119](https://github.com/auth0/omniauth-auth0/pull/119) ([davidpatrick](https://github.com/davidpatrick))
22
23
24 ## [v2.4.2](https://github.com/auth0/omniauth-auth0/tree/v2.4.2) (2021-01-19)
25
26 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.1...v2.4.2)
27
28 **Fixed**
29 - Lock Omniauth to 1.9 in gemspec
30
31 ## [v2.4.1](https://github.com/auth0/omniauth-auth0/tree/v2.4.1) (2020-10-08)
32
33 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.0...v2.4.1)
34
35 **Fixed**
36 - Verify the JWT Signature [\#109](https://github.com/auth0/omniauth-auth0/pull/109) ([jimmyjames](https://github.com/jimmyjames))
37
38
39 ## [v2.4.0](https://github.com/auth0/omniauth-auth0/tree/v2.4.0) (2020-09-22)
40
41 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.3.1...v2.4.0)
42
43 **Security**
44 - Bump rack from 2.2.2 to 2.2.3 [\#107](https://github.com/auth0/omniauth-auth0/pull/107) ([dependabot](https://github.com/dependabot))
45 - Update dependencies [\#100](https://github.com/auth0/omniauth-auth0/pull/100) ([Albalmaceda](https://github.com/Albalmaceda))
46
47 **Added**
48 - Add support for screen_hint=signup param [\#103](https://github.com/auth0/omniauth-auth0/pull/103) ([bbean86](https://github.com/bbean86))
49 - Add support for `connection_scope` in params [\#99](https://github.com/auth0/omniauth-auth0/pull/99) ([felixclack](https://github.com/felixclack))
50
51
52 ## [v2.3.1](https://github.com/auth0/omniauth-auth0/tree/v2.3.1) (2020-03-27)
53
54 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.3.0...v2.3.1)
55
56 **Fixed bugs:**
57
58 - Fixes dependency issue [\#97](https://github.com/auth0/omniauth-auth0/pull/97) ([davidpatrick](https://github.com/davidpatrick))
59 - Fix "NameError: uninitialized constant OmniAuth::Auth0::TokenValidationError" [\#96](https://github.com/auth0/omniauth-auth0/pull/96) ([stefanwork](https://github.com/stefanwork))
60
61 ## [v2.3.0](https://github.com/auth0/omniauth-auth0/tree/v2.3.0) (2020-03-06)
62 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.2.0...v2.3.0)
63
64 **Added**
65 - Improved OIDC Compliance [\#92](https://github.com/auth0/omniauth-auth0/pull/92) ([davidpatrick](https://github.com/davidpatrick))
66
67 ## [v2.2.0](https://github.com/auth0/omniauth-auth0/tree/v2.2.0) (2018-04-18)
68 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.1.0...v2.2.0)
69
70 **Closed issues**
71 - It supports custom domain? [\#71](https://github.com/auth0/omniauth-auth0/issues/71)
72 - Valid Login, No Details: email=nil image=nil name="github|38257089" nickname=nil [\#70](https://github.com/auth0/omniauth-auth0/issues/70)
73
74 **Added**
75 - Custom issuer [\#77](https://github.com/auth0/omniauth-auth0/pull/77) ([ryan-rosenfeld](https://github.com/ryan-rosenfeld))
76 - Add telemetry to token endpoint [\#74](https://github.com/auth0/omniauth-auth0/pull/74) ([joshcanhelp](https://github.com/joshcanhelp))
77
78 **Changed**
79 - Remove telemetry from authorize URL [\#75](https://github.com/auth0/omniauth-auth0/pull/75) ([joshcanhelp](https://github.com/joshcanhelp))
80
81 ## [v2.1.0](https://github.com/auth0/omniauth-auth0/tree/v2.1.0) (2018-10-30)
82 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.0.0...v2.1.0)
83
84 **Closed issues**
85 - URL should be spelled uppercase outside of code [\#64](https://github.com/auth0/omniauth-auth0/issues/64)
86 - Add prompt=none authorization param handler [\#58](https://github.com/auth0/omniauth-auth0/issues/58)
87 - Could not find a valid mapping for path "/auth/oauth2/callback" [\#56](https://github.com/auth0/omniauth-auth0/issues/56)
88 - I had to downgrade my gems to use this strategy :-( [\#53](https://github.com/auth0/omniauth-auth0/issues/53)
89 - CSRF detected [\#49](https://github.com/auth0/omniauth-auth0/issues/49)
90 - /auth/:provider route not registered? [\#47](https://github.com/auth0/omniauth-auth0/issues/47)
91
92 **Added**
93 - Add ID token validation [\#62](https://github.com/auth0/omniauth-auth0/pull/62) ([joshcanhelp](https://github.com/joshcanhelp))
94 - Silent authentication [\#59](https://github.com/auth0/omniauth-auth0/pull/59) ([batalla3692](https://github.com/batalla3692))
95 - Pass connection parameter to auth0 [\#54](https://github.com/auth0/omniauth-auth0/pull/54) ([tomgi](https://github.com/tomgi))
96
97 **Changed**
98 - Update to omniauth-oauth2 [\#55](https://github.com/auth0/omniauth-auth0/pull/55) ([chills42](https://github.com/chills42))
99
100 **Fixed**
101 - Fix Rubocop errors [\#66](https://github.com/auth0/omniauth-auth0/pull/66) ([joshcanhelp](https://github.com/joshcanhelp))
102 - Fix minute bug in README.md [\#63](https://github.com/auth0/omniauth-auth0/pull/63) ([rahuldess](https://github.com/rahuldess))
1103
2104 ## [v2.0.0](https://github.com/auth0/omniauth-auth0/tree/v2.0.0) (2017-01-25)
3105 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v2.0.0)
4106
5107 Updated library to handle OIDC conformant clients and OAuth2 features in Auth0.
6 This affects how the `credentials` and `info` attributes are populated since the payload of /oauth/token and /userinfo are differnt when using OAuth2/OIDC features.
108 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.
7109
8110 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).
9111
15117 - image: `picture` attribute in userinfo response.
16118
17119 Also in `extra` will have in `raw_info` the full /userinfo response.
120
121 **Fixed**
122 - Use image attribute of omniauth instead of picture [\#45](https://github.com/auth0/omniauth-auth0/pull/45) ([hzalaz](https://github.com/hzalaz))
123 - Rework strategy to handle OAuth and OIDC [\#44](https://github.com/auth0/omniauth-auth0/pull/44) ([hzalaz](https://github.com/hzalaz))
124 - lock v10 update, dependencies update [\#41](https://github.com/auth0/omniauth-auth0/pull/41) ([Amialc](https://github.com/Amialc))
125
126 ## [v1.4.2](https://github.com/auth0/omniauth-auth0/tree/v1.4.2) (2016-06-13)
127 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v1.4.2)
128
129 **Added**
130 - Link to OmniAuth site [\#36](https://github.com/auth0/omniauth-auth0/pull/36) ([jghaines](https://github.com/jghaines))
131 - add ssl fix to RoR example [\#31](https://github.com/auth0/omniauth-auth0/pull/31) ([Amialc](https://github.com/Amialc))
132 - Update LICENSE [\#17](https://github.com/auth0/omniauth-auth0/pull/17) ([aguerere](https://github.com/aguerere))
133
134 **Changed**
135 - Update lock to version 9 [\#34](https://github.com/auth0/omniauth-auth0/pull/34) ([Annyv2](https://github.com/Annyv2))
136 - Update Gemfile [\#22](https://github.com/auth0/omniauth-auth0/pull/22) ([Annyv2](https://github.com/Annyv2))
137 - Update lock [\#15](https://github.com/auth0/omniauth-auth0/pull/15) ([Annyv2](https://github.com/Annyv2))
138
139 **Fixed**
140 - Fix setup [\#38](https://github.com/auth0/omniauth-auth0/pull/38) ([deepak](https://github.com/deepak))
141 - Added missing instruction [\#30](https://github.com/auth0/omniauth-auth0/pull/30) ([Annyv2](https://github.com/Annyv2))
142 - Fixes undefined Auth0Lock issue [\#28](https://github.com/auth0/omniauth-auth0/pull/28) ([Annyv2](https://github.com/Annyv2))
143 - Update Readme [\#27](https://github.com/auth0/omniauth-auth0/pull/27) ([Annyv2](https://github.com/Annyv2))
144
18145
19146 ## [v1.4.1](https://github.com/auth0/omniauth-auth0/tree/v1.4.1) (2015-11-18)
20147 [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.0...v1.4.1)
0 # Code of Conduct
1
2 Please see [Auth0's Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) for information on contributing to this repo.
0 # Contribution
1
2 **Thank you in advance for your contribution!**
3
4 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.
5
6 ## Environment setup
7
8 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:
9
10 ```bash
11 $ pwd
12 /PROJECT_ROOT/
13 $ mkdir vendor # if one does not exist
14 $ echo "/vendor/" >> .gitignore
15 $ git clone git@github.com:auth0/omniauth-auth0.git vendor/omniauth-auth0
16 Cloning into 'vendor/omniauth-auth0'...
17 ```
18
19 Now, run the following command in your project root directory:
20
21 ```bash
22 $ bundle config --local local.omniauth-auth0 /PROJECT_ROOT/vendor/omniauth-auth0
23 You are replacing the current local value of local.omniauth-auth0, which is currently nil
24 $ bundle config
25 Settings are listed in order of priority. The top value will be used.
26 local.omniauth-auth0
27 Set for your local app (/PROJECT_ROOT/.bundle/config): "/PROJECT_ROOT/vendor/omniauth-auth0"
28 ```
29
30 Finally, add or change the gem include to add a `github:` param:
31
32 ```ruby
33 source 'https://rubygems.org'
34 # ...
35 # OmniAuth strategy for authenticating with Auth0
36 gem 'omniauth-auth0', github: 'auth0/omniauth-auth0'
37 #..
38 ```
39
40 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.
41
42 [Great explanation for why this setup works well](https://rossta.net/blog/how-to-specify-local-ruby-gems-in-your-gemfile.html).
43
44 ## Testing
45
46 Tests should be added for additional or modified functionality and all tests should run successfully before submitting a PR.
47
48 ### Adding tests
49
50 All new tests should be added to the `/spec/omniauth` directory. Testing resources, like JSON fixtures, should be added to the `/spec/resources` directory.
51
52 ### Running tests
53
54 Running tests is as simple as:
55
56 ```bash
57 $ bundle exec rake spec
58 ```
59
60 ## Documentation
61
62 Documentation for this gem is primarily done at the code level. All new methods should include a docblock at least.
63
64 ## Code quality tools
65
66 Code quality is enforced across the entire gem with Rubocop:
67
68 ```bash
69 $ bundle exec rake rubocop
70 ```
0 source 'http://rubygems.org'
0 source 'https://rubygems.org'
11
22 gemspec
33
44 gem 'gem-release'
5 gem 'jwt'
56 gem 'rake'
67
78 group :development do
89 gem 'dotenv'
910 gem 'pry'
11 gem 'rubocop', require: false
1012 gem 'shotgun'
1113 gem 'sinatra'
1214 gem 'thin'
1416
1517 group :test do
1618 gem 'guard-rspec', require: false
17 gem 'listen', '~> 3.1.5'
19 gem 'listen', '~> 3'
1820 gem 'rack-test'
1921 gem 'rspec', '~> 3.5'
20 gem 'rubocop', '>= 0.30', platforms: [
21 :ruby_19, :ruby_20, :ruby_21, :ruby_22
22 ]
22 gem 'codecov', require: false
2323 gem 'simplecov'
2424 gem 'webmock'
2525 end
0 [![Build Status](https://travis-ci.org/auth0/omniauth-auth0.svg)](https://travis-ci.org/auth0/omniauth-auth0)
1
20 # OmniAuth Auth0
31
4 This is the official [OmniAuth](https://github.com/intridea/omniauth) strategy for authenticating to [Auth0](https://auth0.com).
5
6 ## Installing
7
8 Add to your `Gemfile`:
2 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.
3
4 > :warning: **Important security note:** This solution uses a 3rd party library with an unresolved [security issue(s)](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9284). Please review the details of the vulnerability, including [Auth0](https://github.com/auth0/omniauth-auth0/issues/82 ) and other recommended [mitigations](https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284), before implementing the solution.
5
6 [![CircleCI](https://img.shields.io/circleci/project/github/auth0/omniauth-auth0/master.svg)](https://circleci.com/gh/auth0/omniauth-auth0)
7 [![codecov](https://codecov.io/gh/auth0/omniauth-auth0/branch/master/graph/badge.svg)](https://codecov.io/gh/auth0/omniauth-auth0)
8 [![Gem Version](https://badge.fury.io/rb/omniauth-auth0.svg)](https://badge.fury.io/rb/omniauth-auth0)
9 [![MIT licensed](https://img.shields.io/dub/l/vibe-d.svg?style=flat)](https://github.com/auth0/omniauth-auth0/blob/master/LICENSE)
10 [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fomniauth-auth0.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fomniauth-auth0?ref=badge_shield)
11
12 ## Table of Contents
13
14 - [Documentation](#documentation)
15 - [Installation](#installation)
16 - [Getting Started](#getting-started)
17 - [Contribution](#contribution)
18 - [Support + Feedback](#support--feedback)
19 - [Vulnerability Reporting](#vulnerability-reporting)
20 - [What is Auth0](#what-is-auth0)
21 - [License](#license)
22
23 ## Documentation
24
25 - [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails)
26 - [Sample projects](https://github.com/auth0-samples/auth0-rubyonrails-sample)
27 - [API Reference](https://www.rubydoc.info/gems/omniauth-auth0)
28
29 ## Installation
30
31 Add the following line to your `Gemfile`:
932
1033 ```ruby
1134 gem 'omniauth-auth0'
1235 ```
1336
14 Then `bundle install`.
15
16 ## Usage
17
18 ### Rails
19
20 ```ruby
21 Rails.application.config.middleware.use OmniAuth::Builder do
22 provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
23 end
24 ```
25
26 Then to redirect to your tenant's hosted login page:
27
28 ```ruby
29 redirect_to '/auth/auth0'
30 ```
31
32 ### Sinatra
33
34 ```ruby
35 use OmniAuth::Builder do
36 provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
37 end
38 ```
39
40 Then to redirect to your tenant's hosted login page:
41
42 ```ruby
43 redirect to('/auth/auth0')
44 ```
45
46 > You can customize your hosted login page in your [Auth0 Dashboard](https://manage.auth0.com/#/login_page)
47
48 ### Auth parameters
49
50 To send additional parameters during login you can specify them when you register the provider
51
52 ```ruby
53 provider
37 If you're using this strategy with Rails, also add the following for CSRF protection:
38
39 ```ruby
40 gem 'omniauth-rails_csrf_protection'
41 ```
42
43 Then install:
44
45 ```bash
46 $ bundle install
47 ```
48
49 See our [contributing guide](CONTRIBUTING.md) for information on local installation for development.
50
51 ## Getting Started
52
53 To start processing authentication requests, the following steps must be performed:
54
55 1. Initialize the strategy
56 2. Configure the callback controller
57 3. Add the required routes
58 4. Trigger an authentication request
59
60 All of these tasks and more are covered in our [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails).
61
62 ### Additional authentication parameters
63
64 To send additional parameters during login, you can specify them when you register the provider:
65
66 ```ruby
67 provider
5468 :auth0,
5569 ENV['AUTH0_CLIENT_ID'],
5670 ENV['AUTH0_CLIENT_SECRET'],
5872 {
5973 authorize_params: {
6074 scope: 'openid read:users write:order',
61 audience: 'https://mydomain/api'
75 audience: 'https://mydomain/api',
76 max_age: 3600 # time in seconds authentication is valid
6277 }
6378 }
6479 ```
6580
66 that will tell it to send those parameters on every Auth request.
67
68 Or you can do it for a specific Auth request by adding them in the query parameter of the redirect url:
69
70 ```ruby
71 redirect_to '/auth/auth0?connection=google-oauth2'
72 ```
73
74 ### Auth Hash
75
76 Auth0 strategy will have the standard OmniAuth hash attributes:
77
78 - provider: the name of the strategy, in this case `auth0`
79 - uid: the user identifier
80 - info: the result of the call to /userinfo using OmniAuth standard attributes
81 - credentials: Auth0 tokens, at least will have an access_token but can eventually have refresh_token and/or id_token
82 - extra: Additional info obtained from calling /userinfo in the attribute `raw_info`
83
84 ```ruby
85 {
86 :provider => 'auth0',
87 :uid => 'google-oauth2|this-is-the-google-id',
88 :info => {
89 :name => 'John Foo',
90 :email => 'johnfoo@example.org',
91 :nickname => 'john',
92 :image => 'https://example.org/john.jpg'
93 },
94 :credentials => {
95 :token => 'XdDadllcas2134rdfdsI',
96 :expires_at => 1485373937,
97 :expires => true,
98 :refresh_token => 'aKNajdjfj123nBasd',
99 :id_token => 'eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBGb28ifQ.lxAiy1rqve8ZHQEQVehUlP1sommPHVJDhgPgFPnDosg',
100 :token_type => 'bearer',
101 },
102 :extra => {
103 :raw_info => {
104 :email => 'johnfoo@example.org',
105 :email_verified => 'true',
106 :name => 'John Foo',
107 :picture => 'https://example.org/john.jpg',
108 :user_id => 'google-oauth2|this-is-the-google-id',
109 :nickname => 'john',
110 :created_at: '2014-07-15T17:19:50.387Z'
111 }
112 }
113 }
114 ```
115
116 ### ActionDispatch::Cookies::CookieOverflow issue
117
118 If you are getting this error it means that you are using Cookie sessions and since you are storing the whole profile it overflows the max-size of 4K.
119
120 You can change to use In-Memory store for development as follows:
121
122 # /config/initializers/session_store.rb
123 CrazyApp::Application.config.session_store :cache_store
124
125 # /config/environments/development.rb
126 config.cache_store = :memory_store
127
128 ## Documentation
129
130 For more information about [auth0](http://auth0.com) contact our [documentation page](http://docs.auth0.com/).
131
132 ## Issue Reporting
133
134 If you have found a bug or if you have a feature request, please report them at this repository issues section. 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.
135
136 ## Author
137
138 [Auth0](https://auth0.com)
81 ... which will tell the strategy to send those parameters on every authentication request.
82
83 ### Authentication hash
84
85 The Auth0 strategy will provide the standard OmniAuth hash attributes:
86
87 - `:provider` - the name of the strategy, in this case `auth0`
88 - `:uid` - the user identifier
89 - `:info` - the result of the call to `/userinfo` using OmniAuth standard attributes
90 - `:credentials` - tokens requested and data
91 - `:extra` - Additional info obtained from calling `/userinfo` in the `:raw_info` property
92
93 ```ruby
94 {
95 :provider => 'auth0',
96 :uid => 'auth0|USER_ID',
97 :info => {
98 :name => 'John Foo',
99 :email => 'johnfoo@example.org',
100 :nickname => 'john',
101 :image => 'https://example.org/john.jpg'
102 },
103 :credentials => {
104 :token => 'ACCESS_TOKEN',
105 :expires_at => 1485373937,
106 :expires => true,
107 :refresh_token => 'REFRESH_TOKEN',
108 :id_token => 'JWT_ID_TOKEN',
109 :token_type => 'bearer',
110 },
111 :extra => {
112 :raw_info => {
113 :email => 'johnfoo@example.org',
114 :email_verified => 'true',
115 :name => 'John Foo',
116 :picture => 'https://example.org/john.jpg',
117 :user_id => 'auth0|USER_ID',
118 :nickname => 'john',
119 :created_at => '2014-07-15T17:19:50.387Z'
120 }
121 }
122 }
123 ```
124
125 ### Query Parameter Options
126
127 In some scenarios, you may need to pass specific query parameters to `/authorize`. The following parameters are available to enable this:
128
129 - `connection`
130 - `connection_scope`
131 - `prompt`
132 - `screen_hint` (only relevant to New Universal Login Experience)
133 - `organization`
134 - `invitation`
135
136 Simply pass these query parameters to your OmniAuth redirect endpoint to enable their behavior.
137
138 ## Examples
139
140 ### Auth0 Organizations (Closed Beta)
141
142 Organizations is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications.
143
144 Using Organizations, you can:
145
146 - Represent teams, business customers, partner companies, or any logical grouping of users that should have different ways of accessing your applications, as organizations.
147 - Manage their membership in a variety of ways, including user invitation.
148 - Configure branded, federated login flows for each organization.
149 - Implement role-based access control, such that users can have different roles when authenticating in the context of different organizations.
150 - Build administration capabilities into your products, using Organizations APIs, so that those businesses can manage their own organizations.
151
152 Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans.
153
154 #### Logging in with an Organization
155
156 Logging in with an Organization is as easy as passing the parameters to the authorize endpoint. You can do this with
157
158 ```ruby
159 <%=
160 button_to 'Login', 'auth/auth0',
161 method: :post,
162 params: {
163 # Found in your Auth0 dashboard, under Organization settings:
164 organization: '{AUTH0_ORGANIZATION}'
165 }
166 %>
167 ```
168
169 Alternatively you can configure the organization when you register the provider:
170
171 ```ruby
172 provider
173 :auth0,
174 ENV['AUTH0_CLIENT_ID'],
175 ENV['AUTH0_CLIENT_SECRET'],
176 ENV['AUTH0_DOMAIN'],
177 {
178 authorize_params: {
179 scope: 'openid read:users',
180 audience: 'https://{AUTH0_DOMAIN}/api',
181 organization: '{AUTH0_ORGANIZATION}'
182 }
183 }
184 ```
185
186 #### Accepting user invitations
187
188 Auth0 Organizations allow users to be invited using emailed links, which will direct a user back to your application. The URL the user will arrive at is based on your configured `Application Login URI`, which you can change from your Application's settings inside the Auth0 dashboard.
189
190 When the user arrives at your application using an invite link, you can expect three query parameters to be provided: `invitation`, `organization`, and `organization_name`. These will always be delivered using a GET request.
191
192 You can then supply those parametrs to a `button_to` or `link_to` helper
193
194 ```ruby
195 <%=
196 button_to 'Login', 'auth/auth0',
197 method: :post,
198 params: {
199 organization: '{YOUR_ORGANIZATION_ID}',
200 invitation: '{INVITE_CODE}'
201 }
202 %>
203 ```
204
205 ## Contribution
206
207 We appreciate feedback and contribution to this repo! Before you get started, please see the following:
208
209 - [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
210 - [Auth0's Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
211 - [This repo's contribution guide](CONTRIBUTING.md)
212
213 ## Support + Feedback
214
215 - Use [Community](https://community.auth0.com/) for usage, questions, specific cases.
216 - Use [Issues](https://github.com/auth0/omniauth-auth0/issues) here for code-level support and bug reports.
217 - Paid customers can use [Support](https://support.auth0.com/) to submit a trouble ticket for production-affecting issues.
218
219 ## Vulnerability Reporting
220
221 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.
222
223 ## What is Auth0?
224
225 Auth0 helps you to easily:
226
227 - 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.)
228 - log in users with username/password databases, passwordless, or multi-factor authentication
229 - link multiple user accounts together
230 - generate signed JSON Web Tokens to authorize your API calls and flow the user identity securely
231 - access demographics and analytics detailing how, when, and where users are logging in
232 - enrich user profiles from other data sources using customizable JavaScript rules
233
234 [Why Auth0?](https://auth0.com/why-auth0)
139235
140236 ## License
141237
142 This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.
238 The OmniAuth Auth0 strategy is licensed under MIT - [LICENSE](LICENSE)
239
240
241 [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fomniauth-auth0.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fomniauth-auth0?ref=badge_large)
99 RuboCop::RakeTask.new
1010 rescue LoadError
1111 task :rubocop do
12 $stderr.puts 'Rubocop is disabled'
12 warn 'Rubocop is disabled'
1313 end
1414 end
1515
2222 end
2323
2424 desc 'Run specs'
25 task default: [:spec, :rubocop]
25 task default: %i[spec rubocop]
2626 task test: :spec
2727 task :guard do
2828 system 'bundle exec guard'
0 coverage:
1 precision: 2
2 round: down
3 range: "60...100"
4 status:
5 project:
6 default:
7 enabled: true
8 target: auto
9 threshold: 5%
10 if_no_uploads: error
11 patch:
12 default:
13 enabled: true
14 target: 80%
15 threshold: 30%
16 if_no_uploads: error
17 changes:
18 default:
19 enabled: true
20 if_no_uploads: error
21 comment: false
0 module OmniAuth
1 module Auth0
2 class TokenValidationError < StandardError
3 attr_reader :error_reason
4 def initialize(msg)
5 @error_reason = msg
6 super(msg)
7 end
8 end
9 end
10 end
0 require 'base64'
1 require 'uri'
2 require 'json'
3 require 'omniauth'
4 require 'omniauth/auth0/errors'
5
6 module OmniAuth
7 module Auth0
8 # JWT Validator class
9 class JWTValidator
10 attr_accessor :issuer, :domain
11
12 # Initializer
13 # @param options object
14 # options.domain - Application domain.
15 # options.issuer - Application issuer (optional).
16 # options.client_id - Application Client ID.
17 # options.client_secret - Application Client Secret.
18
19 def initialize(options, authorize_params = {})
20 @domain = uri_string(options.domain)
21
22 # Use custom issuer if provided, otherwise use domain
23 @issuer = @domain
24 @issuer = uri_string(options.issuer) if options.respond_to?(:issuer)
25
26 @client_id = options.client_id
27 @client_secret = options.client_secret
28 end
29
30 # Verify a token's signature. Only tokens signed with the RS256 or HS256 signatures are supported.
31 # Deprecated: Please use `decode` instead
32 # @return array - The token's key and signing algorithm
33 def verify_signature(jwt)
34 head = token_head(jwt)
35 key, alg = extract_key(head)
36
37 # Call decode to verify the signature
38 JWT.decode(jwt, key, true, decode_opts(alg))
39 return key, alg
40 end
41
42 # Decodes a JWT and verifies it's signature. Only tokens signed with the RS256 or HS256 signatures are supported.
43 # @param jwt string - JWT to verify.
44 # @return hash - The decoded token, if there were no exceptions.
45 # @see https://github.com/jwt/ruby-jwt
46 def decode(jwt)
47 head = token_head(jwt)
48 key, alg = extract_key(head)
49
50 # Call decode to verify the signature
51 JWT.decode(jwt, key, true, decode_opts(alg))
52 end
53
54 # Verify a JWT.
55 # @param jwt string - JWT to verify.
56 # @param authorize_params hash - Authorization params to verify on the JWT
57 # @return hash - The verified token payload, if there were no exceptions.
58 def verify(jwt, authorize_params = {})
59 if !jwt
60 raise OmniAuth::Auth0::TokenValidationError.new('ID token is required but missing')
61 end
62
63 parts = jwt.split('.')
64 if parts.length != 3
65 raise OmniAuth::Auth0::TokenValidationError.new('ID token could not be decoded')
66 end
67
68 id_token, header = decode(jwt)
69 verify_claims(id_token, authorize_params)
70
71 return id_token
72 end
73
74 # Get the decoded head segment from a JWT.
75 # @return hash - The parsed head of the JWT passed, empty hash if not.
76 def token_head(jwt)
77 jwt_parts = jwt.split('.')
78 return {} if blank?(jwt_parts) || blank?(jwt_parts[0])
79
80 json_parse(Base64.decode64(jwt_parts[0]))
81 end
82
83 # Get the JWKS from the issuer and return a public key.
84 # @param x5c string - X.509 certificate chain from a JWKS.
85 # @return key - The X.509 certificate public key.
86 def jwks_public_cert(x5c)
87 x5c = Base64.decode64(x5c)
88
89 # https://docs.ruby-lang.org/en/2.4.0/OpenSSL/X509/Certificate.html
90 OpenSSL::X509::Certificate.new(x5c).public_key
91 end
92
93 # Return a specific key from a JWKS object.
94 # @param key string - Key to find in the JWKS.
95 # @param kid string - Key ID to identify the right JWK.
96 # @return nil|string
97 def jwks_key(key, kid)
98 return nil if blank?(jwks[:keys])
99
100 matching_jwk = jwks[:keys].find { |jwk| jwk[:kid] == kid }
101 matching_jwk[key] if matching_jwk
102 end
103
104 private
105 # Get the JWT decode options. We disable the claim checks since we perform our claim validation logic
106 # Docs: https://github.com/jwt/ruby-jwt
107 # @return hash
108 def decode_opts(alg)
109 {
110 algorithm: alg,
111 verify_expiration: false,
112 verify_iat: false,
113 verify_iss: false,
114 verify_aud: false,
115 verify_jti: false,
116 verify_subj: false,
117 verify_not_before: false
118 }
119 end
120
121 def extract_key(head)
122 if head[:alg] == 'RS256'
123 key, alg = [rs256_decode_key(head[:kid]), head[:alg]]
124 elsif head[:alg] == 'HS256'
125 key, alg = [@client_secret, head[:alg]]
126 else
127 raise OmniAuth::Auth0::TokenValidationError.new("Signature algorithm of #{head[:alg]} is not supported. Expected the ID token to be signed with RS256 or HS256")
128 end
129 end
130
131 def rs256_decode_key(kid)
132 jwks_x5c = jwks_key(:x5c, kid)
133
134 if jwks_x5c.nil?
135 raise OmniAuth::Auth0::TokenValidationError.new("Could not find a public key for Key ID (kid) '#{kid}'")
136 end
137
138 jwks_public_cert(jwks_x5c.first)
139 end
140
141 # Get a JWKS from the domain
142 # @return void
143 def jwks
144 jwks_uri = URI(@domain + '.well-known/jwks.json')
145 @jwks ||= json_parse(Net::HTTP.get(jwks_uri))
146 end
147
148 # Rails Active Support blank method.
149 # @param obj object - Object to check for blankness.
150 # @return boolean
151 def blank?(obj)
152 obj.respond_to?(:empty?) ? obj.empty? : !obj
153 end
154
155 # Parse JSON with symbolized names.
156 # @param json string - JSON to parse.
157 # @return hash
158 def json_parse(json)
159 JSON.parse(json, symbolize_names: true)
160 end
161
162 # Parse a URI into the desired string format
163 # @param uri - the URI to parse
164 # @return string
165 def uri_string(uri)
166 temp_domain = URI(uri)
167 temp_domain = URI("https://#{uri}") unless temp_domain.scheme
168 temp_domain = temp_domain.to_s
169 temp_domain.end_with?('/') ? temp_domain : "#{temp_domain}/"
170 end
171
172 def verify_claims(id_token, authorize_params)
173 leeway = authorize_params[:leeway] || 60
174 max_age = authorize_params[:max_age]
175 nonce = authorize_params[:nonce]
176 organization = authorize_params[:organization]
177
178 verify_iss(id_token)
179 verify_sub(id_token)
180 verify_aud(id_token)
181 verify_expiration(id_token, leeway)
182 verify_iat(id_token)
183 verify_nonce(id_token, nonce)
184 verify_azp(id_token)
185 verify_auth_time(id_token, leeway, max_age)
186 verify_org(id_token, organization)
187 end
188
189 def verify_iss(id_token)
190 issuer = id_token['iss']
191 if !issuer
192 raise OmniAuth::Auth0::TokenValidationError.new("Issuer (iss) claim must be a string present in the ID token")
193 elsif @issuer != issuer
194 raise OmniAuth::Auth0::TokenValidationError.new("Issuer (iss) claim mismatch in the ID token, expected (#{@issuer}), found (#{id_token['iss']})")
195 end
196 end
197
198 def verify_sub(id_token)
199 subject = id_token['sub']
200 if !subject || !subject.is_a?(String) || subject.empty?
201 raise OmniAuth::Auth0::TokenValidationError.new('Subject (sub) claim must be a string present in the ID token')
202 end
203 end
204
205 def verify_aud(id_token)
206 audience = id_token['aud']
207 if !audience || !(audience.is_a?(String) || audience.is_a?(Array))
208 raise OmniAuth::Auth0::TokenValidationError.new("Audience (aud) claim must be a string or array of strings present in the ID token")
209 elsif audience.is_a?(Array) && !audience.include?(@client_id)
210 raise OmniAuth::Auth0::TokenValidationError.new("Audience (aud) claim mismatch in the ID token; expected #{@client_id} but was not one of #{audience.join(', ')}")
211 elsif audience.is_a?(String) && audience != @client_id
212 raise OmniAuth::Auth0::TokenValidationError.new("Audience (aud) claim mismatch in the ID token; expected #{@client_id} but found #{audience}")
213 end
214 end
215
216 def verify_expiration(id_token, leeway)
217 expiration = id_token['exp']
218 if !expiration || !expiration.is_a?(Integer)
219 raise OmniAuth::Auth0::TokenValidationError.new("Expiration time (exp) claim must be a number present in the ID token")
220 elsif expiration <= Time.now.to_i - leeway
221 raise OmniAuth::Auth0::TokenValidationError.new("Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(expiration + leeway)})")
222 end
223 end
224
225 def verify_iat(id_token)
226 if !id_token['iat']
227 raise OmniAuth::Auth0::TokenValidationError.new("Issued At (iat) claim must be a number present in the ID token")
228 end
229 end
230
231 def verify_nonce(id_token, nonce)
232 if nonce
233 received_nonce = id_token['nonce']
234 if !received_nonce
235 raise OmniAuth::Auth0::TokenValidationError.new("Nonce (nonce) claim must be a string present in the ID token")
236 elsif nonce != received_nonce
237 raise OmniAuth::Auth0::TokenValidationError.new("Nonce (nonce) claim value mismatch in the ID token; expected (#{nonce}), found (#{received_nonce})")
238 end
239 end
240 end
241
242 def verify_azp(id_token)
243 audience = id_token['aud']
244 if audience.is_a?(Array) && audience.length > 1
245 azp = id_token['azp']
246 if !azp || !azp.is_a?(String)
247 raise OmniAuth::Auth0::TokenValidationError.new("Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values")
248 elsif azp != @client_id
249 raise OmniAuth::Auth0::TokenValidationError.new("Authorized Party (azp) claim mismatch in the ID token; expected (#{@client_id}), found (#{azp})")
250 end
251 end
252 end
253
254 def verify_auth_time(id_token, leeway, max_age)
255 if max_age
256 auth_time = id_token['auth_time']
257 if !auth_time || !auth_time.is_a?(Integer)
258 raise OmniAuth::Auth0::TokenValidationError.new("Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified")
259 elsif Time.now.to_i > auth_time + max_age + leeway;
260 raise OmniAuth::Auth0::TokenValidationError.new("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (#{Time.now}) is after last auth time (#{Time.at(auth_time + max_age + leeway)})")
261 end
262 end
263 end
264
265 def verify_org(id_token, organization)
266 if organization
267 org_id = id_token['org_id']
268 if !org_id || !org_id.is_a?(String)
269 raise OmniAuth::Auth0::TokenValidationError.new("Organization Id (org_id) claim must be a string present in the ID token")
270 elsif org_id != organization
271 raise OmniAuth::Auth0::TokenValidationError.new("Organization Id (org_id) claim value mismatch in the ID token; expected '#{organization}', found '#{org_id}'")
272 end
273 end
274 end
275 end
276 end
277 end
0 require 'json'
1
2 module OmniAuth
3 module Auth0
4 # Module to provide necessary telemetry for API requests.
5 module Telemetry
6
7 # Return a telemetry hash to be encoded and sent to Auth0.
8 # @return hash
9 def telemetry
10 telemetry = {
11 name: 'omniauth-auth0',
12 version: OmniAuth::Auth0::VERSION,
13 env: {
14 ruby: RUBY_VERSION
15 }
16 }
17 add_rails_version telemetry
18 end
19
20 # JSON-ify and base64 encode the current telemetry.
21 # @return string
22 def telemetry_encoded
23 Base64.urlsafe_encode64(JSON.dump(telemetry))
24 end
25
26 private
27
28 def add_rails_version(telemetry)
29 return telemetry unless Gem.loaded_specs['rails'].respond_to? :version
30 telemetry[:env][:rails] = Gem.loaded_specs['rails'].version.to_s
31 telemetry
32 end
33 end
34 end
35 end
0 # frozen_string_literal: true
1
02 require 'base64'
13 require 'uri'
4 require 'securerandom'
25 require 'omniauth-oauth2'
6 require 'omniauth/auth0/jwt_validator'
7 require 'omniauth/auth0/telemetry'
8 require 'omniauth/auth0/errors'
39
410 module OmniAuth
511 module Strategies
612 # Auth0 OmniAuth strategy
713 class Auth0 < OmniAuth::Strategies::OAuth2
14 include OmniAuth::Auth0::Telemetry
15
816 option :name, 'auth0'
917
10 args [
11 :client_id,
12 :client_secret,
13 :domain
18 args %i[
19 client_id
20 client_secret
21 domain
1422 ]
1523
24 # Setup client URLs used during authentication
1625 def client
1726 options.client_options.site = domain_url
1827 options.client_options.authorize_url = '/authorize'
2130 super
2231 end
2332
33 # Use the "sub" key of the userinfo returned
34 # as the uid (globally unique string identifier).
2435 uid { raw_info['sub'] }
2536
37 # Build the API credentials hash with returned auth data.
2638 credentials do
27 hash = { 'token' => access_token.token }
28 hash['expires'] = true
39 credentials = {
40 'token' => access_token.token,
41 'expires' => true
42 }
43
2944 if access_token.params
30 hash['id_token'] = access_token.params['id_token']
31 hash['token_type'] = access_token.params['token_type']
32 hash['refresh_token'] = access_token.refresh_token
45 credentials.merge!(
46 'id_token' => access_token.params['id_token'],
47 'token_type' => access_token.params['token_type'],
48 'refresh_token' => access_token.refresh_token
49 )
3350 end
34 hash
51
52 # Retrieve and remove authorization params from the session
53 session_authorize_params = session['authorize_params'] || {}
54 session.delete('authorize_params')
55
56 auth_scope = session_authorize_params[:scope]
57 if auth_scope.respond_to?(:include?) && auth_scope.include?('openid')
58 # Make sure the ID token can be verified and decoded.
59 jwt_validator.verify(credentials['id_token'], session_authorize_params)
60 end
61
62 credentials
3563 end
3664
65 # Store all raw information for use in the session.
3766 extra do
3867 {
3968 raw_info: raw_info
4069 }
4170 end
4271
72 # Build a hash of information about the user
73 # with keys taken from the Auth Hash Schema.
4374 info do
4475 {
4576 name: raw_info['name'] || raw_info['sub'],
4980 }
5081 end
5182
83 # Define the parameters used for the /authorize endpoint
5284 def authorize_params
5385 params = super
54 params['auth0Client'] = client_info
86 %w[connection connection_scope prompt screen_hint login_hint organization invitation].each do |key|
87 params[key] = request.params[key] if request.params.key?(key)
88 end
89
90 # Generate nonce
91 params[:nonce] = SecureRandom.hex
92 # Generate leeway if none exists
93 params[:leeway] = 60 unless params[:leeway]
94
95 # Store authorize params in the session for token verification
96 session['authorize_params'] = params
97
5598 params
5699 end
57100
101 def build_access_token
102 options.token_params[:headers] = { 'Auth0-Client' => telemetry_encoded }
103 super
104 end
105
106 # Declarative override for the request phase of authentication
58107 def request_phase
59108 if no_client_id?
109 # Do we have a client_id for this Application?
60110 fail!(:missing_client_id)
61111 elsif no_client_secret?
112 # Do we have a client_secret for this Application?
62113 fail!(:missing_client_secret)
63114 elsif no_domain?
115 # Do we have a domain for this Application?
64116 fail!(:missing_domain)
65117 else
118 # All checks pass, run the Oauth2 request_phase method.
66119 super
67120 end
68121 end
69122
70 private
71
72 def raw_info
73 userinfo_url = options.client_options.userinfo_url
74 @raw_info ||= access_token.get(userinfo_url).parsed
123 def callback_phase
124 super
125 rescue OmniAuth::Auth0::TokenValidationError => e
126 fail!(:token_validation_error, e)
75127 end
76128
129 private
130 def jwt_validator
131 @jwt_validator ||= OmniAuth::Auth0::JWTValidator.new(options)
132 end
133
134 # Parse the raw user info.
135 def raw_info
136 return @raw_info if @raw_info
137
138 if access_token["id_token"]
139 claims, header = jwt_validator.decode(access_token["id_token"])
140 @raw_info = claims
141 else
142 userinfo_url = options.client_options.userinfo_url
143 @raw_info = access_token.get(userinfo_url).parsed
144 end
145
146 return @raw_info
147 end
148
149 # Check if the options include a client_id
77150 def no_client_id?
78151 ['', nil].include?(options.client_id)
79152 end
80153
154 # Check if the options include a client_secret
81155 def no_client_secret?
82156 ['', nil].include?(options.client_secret)
83157 end
84158
159 # Check if the options include a domain
85160 def no_domain?
86161 ['', nil].include?(options.domain)
87162 end
88163
164 # Normalize a domain to a URL.
89165 def domain_url
90166 domain_url = URI(options.domain)
91167 domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
92168 domain_url.to_s
93169 end
94
95 def client_info
96 client_info = JSON.dump(
97 name: 'omniauth-auth0',
98 version: OmniAuth::Auth0::VERSION
99 )
100 Base64.urlsafe_encode64(client_info)
101 end
102170 end
103171 end
104172 end
00 module OmniAuth
11 module Auth0
2 VERSION = '2.0.0'.freeze
2 VERSION = '2.6.0'.freeze
33 end
44 end
0 require 'omniauth-auth0/version' # rubocop:disable Style/FileName
0 require 'omniauth-auth0/version'
11 require 'omniauth/strategies/auth0'
77 s.authors = ['Auth0']
88 s.email = ['info@auth0.com']
99 s.homepage = 'https://github.com/auth0/omniauth-auth0'
10 s.summary = 'Omniauth OAuth2 strategy for the Auth0 platform.'
10 s.summary = 'OmniAuth OAuth2 strategy for the Auth0 platform.'
1111 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.
1212
1313 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.
1414
15 omniauth-auth0 is the omniauth strategy for Auth0.
15 omniauth-auth0 is the OmniAuth strategy for Auth0.
1616 }
17
18 s.rubyforge_project = 'omniauth-auth0'
1917
2018 s.files = `git ls-files`.split("\n")
2119 s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
2220 s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
2321 s.require_paths = ['lib']
2422
25 s.add_runtime_dependency 'omniauth-oauth2', '~> 1.4'
23 s.add_runtime_dependency 'omniauth', '~> 1.9'
24 s.add_runtime_dependency 'omniauth-oauth2', '~> 1.5'
2625
27 s.add_development_dependency 'bundler', '~> 1.9'
26 s.add_development_dependency 'bundler'
2827
2928 s.license = 'MIT'
3029 end
0 require 'spec_helper'
1 require 'json'
2 require 'jwt'
3
4 describe OmniAuth::Auth0::JWTValidator do
5 #
6 # Reused data
7 #
8
9 let(:client_id) { 'CLIENT_ID' }
10 let(:client_secret) { 'CLIENT_SECRET' }
11 let(:domain) { 'samples.auth0.com' }
12 let(:future_timecode) { 32_503_680_000 }
13 let(:past_timecode) { 303_912_000 }
14 let(:valid_jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
15
16 let(:rsa_private_key) do
17 OpenSSL::PKey::RSA.generate 2048
18 end
19
20 let(:valid_jwks) do
21 {
22 keys: [
23 {
24 kid: valid_jwks_kid,
25 x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)]
26 }
27 ]
28 }.to_json
29 end
30
31 let(:jwks) do
32 current_dir = File.dirname(__FILE__)
33 jwks_file = File.read("#{current_dir}/../../resources/jwks.json")
34 JSON.parse(jwks_file, symbolize_names: true)
35 end
36
37 #
38 # Specs
39 #
40
41 describe 'JWT verifier default values' do
42 let(:jwt_validator) do
43 make_jwt_validator
44 end
45
46 it 'should have the correct issuer' do
47 expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
48 end
49 end
50
51 describe 'JWT verifier token_head' do
52 let(:jwt_validator) do
53 make_jwt_validator
54 end
55
56 it 'should parse the head of a valid JWT' do
57 expect(jwt_validator.token_head(make_hs256_token)[:alg]).to eq('HS256')
58 end
59
60 it 'should fail parsing the head of a blank JWT' do
61 expect(jwt_validator.token_head('')).to eq({})
62 end
63
64 it 'should fail parsing the head of an invalid JWT' do
65 expect(jwt_validator.token_head('.')).to eq({})
66 end
67
68 it 'should throw an exception for invalid JSON' do
69 expect do
70 jwt_validator.token_head('QXV0aDA=')
71 end.to raise_error(JSON::ParserError)
72 end
73 end
74
75 describe 'JWT verifier jwks_public_cert' do
76 let(:jwt_validator) do
77 make_jwt_validator
78 end
79
80 it 'should return a public_key' do
81 x5c = jwks[:keys].first[:x5c].first
82 public_cert = jwt_validator.jwks_public_cert(x5c)
83 expect(public_cert.instance_of?(OpenSSL::PKey::RSA)).to eq(true)
84 end
85
86 it 'should fail with an invalid x5c' do
87 expect do
88 jwt_validator.jwks_public_cert('QXV0aDA=')
89 end.to raise_error(OpenSSL::X509::CertificateError)
90 end
91 end
92
93 describe 'JWT verifier jwks key parsing' do
94 let(:jwt_validator) do
95 make_jwt_validator
96 end
97
98 before do
99 stub_complete_jwks
100 end
101
102 it 'should return a key' do
103 expect(jwt_validator.jwks_key(:alg, valid_jwks_kid)).to eq('RS256')
104 end
105
106 it 'should return an x5c key' do
107 expect(jwt_validator.jwks_key(:x5c, valid_jwks_kid).length).to eq(1)
108 end
109
110 it 'should return nil if there is not key' do
111 expect(jwt_validator.jwks_key(:auth0, valid_jwks_kid)).to eq(nil)
112 end
113
114 it 'should return nil if the key ID is invalid' do
115 expect(jwt_validator.jwks_key(:alg, "#{valid_jwks_kid}_invalid")).to eq(nil)
116 end
117 end
118
119 describe 'JWT verifier custom issuer' do
120 context 'same as domain' do
121 let(:jwt_validator) do
122 make_jwt_validator(opt_issuer: domain)
123 end
124
125 it 'should have the correct issuer' do
126 expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
127 end
128
129 it 'should have the correct domain' do
130 expect(jwt_validator.issuer).to eq('https://samples.auth0.com/')
131 end
132 end
133
134 context 'different from domain' do
135 shared_examples_for 'has correct issuer and domain' do
136 let(:jwt_validator) { make_jwt_validator(opt_issuer: opt_issuer) }
137
138 it 'should have the correct issuer' do
139 expect(jwt_validator.issuer).to eq('https://different.auth0.com/')
140 end
141
142 it 'should have the correct domain' do
143 expect(jwt_validator.domain).to eq('https://samples.auth0.com/')
144 end
145 end
146
147 context 'without protocol and trailing slash' do
148 let(:opt_issuer) { 'different.auth0.com' }
149 it_behaves_like 'has correct issuer and domain'
150 end
151
152 context 'with protocol and trailing slash' do
153 let(:opt_issuer) { 'https://different.auth0.com/' }
154 it_behaves_like 'has correct issuer and domain'
155 end
156 end
157 end
158
159 describe 'JWT verifier verify' do
160 let(:jwt_validator) do
161 make_jwt_validator
162 end
163
164 before do
165 stub_complete_jwks
166 stub_expected_jwks
167 end
168
169 it 'should fail when JWT is nil' do
170 expect do
171 jwt_validator.verify(nil)
172 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
173 message: "ID token is required but missing"
174 }))
175 end
176
177 it 'should fail when JWT is not well-formed' do
178 expect do
179 jwt_validator.verify('abc.123')
180 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
181 message: "ID token could not be decoded"
182 }))
183 end
184
185 it 'should fail with missing issuer' do
186 expect do
187 jwt_validator.verify(make_hs256_token)
188 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
189 message: "Issuer (iss) claim must be a string present in the ID token"
190 }))
191 end
192
193 it 'should fail with invalid issuer' do
194 payload = {
195 iss: 'https://auth0.com/'
196 }
197 token = make_hs256_token(payload)
198 expect do
199 jwt_validator.verify(token)
200 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
201 message: "Issuer (iss) claim mismatch in the ID token, expected (https://samples.auth0.com/), found (https://auth0.com/)"
202 }))
203 end
204
205 it 'should fail when subject is missing' do
206 payload = {
207 iss: "https://#{domain}/",
208 sub: ''
209 }
210 token = make_hs256_token(payload)
211 expect do
212 jwt_validator.verify(token)
213 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
214 message: "Subject (sub) claim must be a string present in the ID token"
215 }))
216 end
217
218 it 'should fail with missing audience' do
219 payload = {
220 iss: "https://#{domain}/",
221 sub: 'sub'
222 }
223 token = make_hs256_token(payload)
224 expect do
225 jwt_validator.verify(token)
226 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
227 message: "Audience (aud) claim must be a string or array of strings present in the ID token"
228 }))
229 end
230
231 it 'should fail with invalid audience' do
232 payload = {
233 iss: "https://#{domain}/",
234 sub: 'sub',
235 aud: 'Auth0'
236 }
237 token = make_hs256_token(payload)
238 expect do
239 jwt_validator.verify(token)
240 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
241 message: "Audience (aud) claim mismatch in the ID token; expected #{client_id} but found Auth0"
242 }))
243 end
244
245 it 'should fail when missing expiration' do
246 payload = {
247 iss: "https://#{domain}/",
248 sub: 'sub',
249 aud: client_id
250 }
251
252 token = make_hs256_token(payload)
253 expect do
254 jwt_validator.verify(token)
255 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
256 message: "Expiration time (exp) claim must be a number present in the ID token"
257 }))
258 end
259
260 it 'should fail when past expiration' do
261 payload = {
262 iss: "https://#{domain}/",
263 sub: 'sub',
264 aud: client_id,
265 exp: past_timecode
266 }
267
268 token = make_hs256_token(payload)
269 expect do
270 jwt_validator.verify(token)
271 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
272 message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(past_timecode + 60)})"
273 }))
274 end
275
276 it 'should pass when past expiration but within default leeway' do
277 exp = Time.now.to_i - 59
278 payload = {
279 iss: "https://#{domain}/",
280 sub: 'sub',
281 aud: client_id,
282 exp: exp,
283 iat: past_timecode
284 }
285
286 token = make_hs256_token(payload)
287 id_token = jwt_validator.verify(token)
288 expect(id_token['exp']).to eq(exp)
289 end
290
291 it 'should fail when past expiration and outside default leeway' do
292 exp = Time.now.to_i - 61
293 payload = {
294 iss: "https://#{domain}/",
295 sub: 'sub',
296 aud: client_id,
297 exp: exp,
298 iat: past_timecode
299 }
300
301 token = make_hs256_token(payload)
302 expect do
303 jwt_validator.verify(token)
304 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
305 message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(exp + 60)})"
306 }))
307 end
308
309 it 'should fail when missing iat' do
310 payload = {
311 iss: "https://#{domain}/",
312 sub: 'sub',
313 aud: client_id,
314 exp: future_timecode
315 }
316
317 token = make_hs256_token(payload)
318 expect do
319 jwt_validator.verify(token)
320 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
321 message: "Issued At (iat) claim must be a number present in the ID token"
322 }))
323 end
324
325 it 'should fail when authorize params has nonce but nonce is missing in the token' do
326 payload = {
327 iss: "https://#{domain}/",
328 sub: 'sub',
329 aud: client_id,
330 exp: future_timecode,
331 iat: past_timecode
332 }
333
334 token = make_hs256_token(payload)
335 expect do
336 jwt_validator.verify(token, { nonce: 'noncey' })
337 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
338 message: "Nonce (nonce) claim must be a string present in the ID token"
339 }))
340 end
341
342 it 'should fail when authorize params has nonce but token nonce does not match' do
343 payload = {
344 iss: "https://#{domain}/",
345 sub: 'sub',
346 aud: client_id,
347 exp: future_timecode,
348 iat: past_timecode,
349 nonce: 'mismatch'
350 }
351
352 token = make_hs256_token(payload)
353 expect do
354 jwt_validator.verify(token, { nonce: 'noncey' })
355 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
356 message: "Nonce (nonce) claim value mismatch in the ID token; expected (noncey), found (mismatch)"
357 }))
358 end
359
360 it 'should fail when “aud” is an array of strings and azp claim is not present' do
361 aud = [
362 client_id,
363 "https://#{domain}/userinfo"
364 ]
365 payload = {
366 iss: "https://#{domain}/",
367 sub: 'sub',
368 aud: aud,
369 exp: future_timecode,
370 iat: past_timecode
371 }
372
373 token = make_hs256_token(payload)
374 expect do
375 jwt_validator.verify(token)
376 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
377 message: "Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values"
378 }))
379 end
380
381 it 'should fail when "azp" claim doesnt match the expected aud' do
382 aud = [
383 client_id,
384 "https://#{domain}/userinfo"
385 ]
386 payload = {
387 iss: "https://#{domain}/",
388 sub: 'sub',
389 aud: aud,
390 exp: future_timecode,
391 iat: past_timecode,
392 azp: 'not_expected'
393 }
394
395 token = make_hs256_token(payload)
396 expect do
397 jwt_validator.verify(token)
398 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
399 message: "Authorized Party (azp) claim mismatch in the ID token; expected (#{client_id}), found (not_expected)"
400 }))
401 end
402
403 it 'should fail when “max_age” sent on the authentication request and this claim is not present' do
404 payload = {
405 iss: "https://#{domain}/",
406 sub: 'sub',
407 aud: client_id,
408 exp: future_timecode,
409 iat: past_timecode
410 }
411
412 token = make_hs256_token(payload)
413 expect do
414 jwt_validator.verify(token, { max_age: 60 })
415 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
416 message: "Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified"
417 }))
418 end
419
420 it 'should fail when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future' do
421 payload = {
422 iss: "https://#{domain}/",
423 sub: 'sub',
424 aud: client_id,
425 exp: future_timecode,
426 iat: past_timecode,
427 auth_time: past_timecode
428 }
429
430 token = make_hs256_token(payload)
431 expect do
432 jwt_validator.verify(token, { max_age: 60 })
433 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
434 message: "Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (#{Time.now}) is after last auth time (#{Time.at(past_timecode + 60 + 60)})"
435 }))
436 end
437
438 it 'should fail when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future, outside the default leeway' do
439 now = Time.now.to_i
440 auth_time = now - 121
441 max_age = 60
442 payload = {
443 iss: "https://#{domain}/",
444 sub: 'sub',
445 aud: client_id,
446 exp: future_timecode,
447 iat: past_timecode,
448 auth_time: auth_time
449 }
450
451 token = make_hs256_token(payload)
452 expect do
453 jwt_validator.verify(token, { max_age: max_age })
454 # Time.at(auth_time + max_age + leeway
455 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
456 message: "Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (#{Time.now}) is after last auth time (#{Time.at(auth_time + max_age + 60)})"
457 }))
458 end
459
460 it 'should verify when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future, outside the default leeway' do
461 now = Time.now.to_i
462 auth_time = now - 119
463 max_age = 60
464 payload = {
465 iss: "https://#{domain}/",
466 sub: 'sub',
467 aud: client_id,
468 exp: future_timecode,
469 iat: past_timecode,
470 auth_time: auth_time
471 }
472
473 token = make_hs256_token(payload)
474 id_token = jwt_validator.verify(token, { max_age: max_age })
475 expect(id_token['auth_time']).to eq(auth_time)
476 end
477
478 it 'should fail when authorize params has organization but org_id is missing in the token', focus: true do
479 payload = {
480 iss: "https://#{domain}/",
481 sub: 'sub',
482 aud: client_id,
483 exp: future_timecode,
484 iat: past_timecode
485 }
486
487 token = make_hs256_token(payload)
488 expect do
489 jwt_validator.verify(token, { organization: 'Test Org' })
490 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
491 message: "Organization Id (org_id) claim must be a string present in the ID token"
492 }))
493 end
494
495 it 'should fail when authorize params has organization but token org_id does not match', focus: true do
496 payload = {
497 iss: "https://#{domain}/",
498 sub: 'sub',
499 aud: client_id,
500 exp: future_timecode,
501 iat: past_timecode,
502 org_id: 'Wrong Org'
503 }
504
505 token = make_hs256_token(payload)
506 expect do
507 jwt_validator.verify(token, { organization: 'Test Org' })
508 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
509 message: "Organization Id (org_id) claim value mismatch in the ID token; expected 'Test Org', found 'Wrong Org'"
510 }))
511 end
512
513 it 'should fail for RS256 token when kid is incorrect' do
514 domain = 'example.org'
515 sub = 'abc123'
516 payload = {
517 sub: sub,
518 exp: future_timecode,
519 iss: "https://#{domain}/",
520 iat: past_timecode,
521 aud: client_id
522 }
523 invalid_kid = 'invalid-kid'
524 token = make_rs256_token(payload, invalid_kid)
525 expect do
526 verified_token = make_jwt_validator(opt_domain: domain).verify(token)
527 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
528 message: "Could not find a public key for Key ID (kid) 'invalid-kid'"
529 }))
530 end
531
532 it 'should fail when RS256 token has invalid signature' do
533 domain = 'example.org'
534 sub = 'abc123'
535 payload = {
536 sub: sub,
537 exp: future_timecode,
538 iss: "https://#{domain}/",
539 iat: past_timecode,
540 aud: client_id
541 }
542 token = make_rs256_token(payload) + 'bad'
543 expect do
544 verified_token = make_jwt_validator(opt_domain: domain).verify(token)
545 end.to raise_error(an_instance_of(JWT::VerificationError).and having_attributes({
546 message: "Signature verification raised"
547 }))
548 end
549
550 it 'should fail when algorithm is not RS256 or HS256' do
551 payload = {
552 iss: "https://#{domain}/",
553 sub: 'abc123',
554 aud: client_id,
555 exp: future_timecode,
556 iat: past_timecode
557 }
558 token = JWT.encode payload, 'secret', 'HS384'
559 expect do
560 jwt_validator.verify(token)
561 end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
562 message: "Signature algorithm of HS384 is not supported. Expected the ID token to be signed with RS256 or HS256"
563 }))
564 end
565
566 it 'should fail when HS256 token has invalid signature' do
567 payload = {
568 iss: "https://#{domain}/",
569 sub: 'abc123',
570 aud: client_id,
571 exp: future_timecode,
572 iat: past_timecode
573 }
574 token = make_hs256_token(payload, 'bad_secret')
575 expect do
576 # validator is configured to use "CLIENT_SECRET" by default
577 jwt_validator.verify(token)
578 end.to raise_error(an_instance_of(JWT::VerificationError))
579 end
580
581 it 'should verify a valid HS256 token with multiple audiences' do
582 audience = [
583 client_id,
584 "https://#{domain}/userinfo"
585 ]
586 payload = {
587 iss: "https://#{domain}/",
588 sub: 'sub',
589 aud: audience,
590 exp: future_timecode,
591 iat: past_timecode,
592 azp: client_id
593 }
594 token = make_hs256_token(payload)
595 id_token = jwt_validator.verify(token)
596 expect(id_token['aud']).to eq(audience)
597 end
598
599 it 'should verify a standard HS256 token' do
600 sub = 'abc123'
601 payload = {
602 iss: "https://#{domain}/",
603 sub: sub,
604 aud: client_id,
605 exp: future_timecode,
606 iat: past_timecode
607 }
608 token = make_hs256_token(payload)
609 verified_token = jwt_validator.verify(token)
610 expect(verified_token['sub']).to eq(sub)
611 end
612
613 it 'should verify a standard RS256 token' do
614 domain = 'example.org'
615 sub = 'abc123'
616 payload = {
617 sub: sub,
618 exp: future_timecode,
619 iss: "https://#{domain}/",
620 iat: past_timecode,
621 aud: client_id
622 }
623 token = make_rs256_token(payload)
624 verified_token = make_jwt_validator(opt_domain: domain).verify(token)
625 expect(verified_token['sub']).to eq(sub)
626 end
627
628 it 'should verify a HS256 JWT signature when calling verify signature directly' do
629 sub = 'abc123'
630 payload = {
631 iss: "https://#{domain}/",
632 sub: sub,
633 aud: client_id,
634 exp: future_timecode,
635 iat: past_timecode
636 }
637 token = make_hs256_token(payload)
638 verified_token_signature = jwt_validator.verify_signature(token)
639 expect(verified_token_signature[0]).to eq('CLIENT_SECRET')
640 expect(verified_token_signature[1]).to eq('HS256')
641 end
642
643 it 'should verify a RS256 JWT signature verify signature directly' do
644 domain = 'example.org'
645 sub = 'abc123'
646 payload = {
647 sub: sub,
648 exp: future_timecode,
649 iss: "https://#{domain}/",
650 iat: past_timecode,
651 aud: client_id
652 }
653 token = make_rs256_token(payload)
654 verified_token_signature = make_jwt_validator(opt_domain: domain).verify_signature(token)
655 expect(verified_token_signature.length).to be(2)
656 expect(verified_token_signature[0]).to be_a(OpenSSL::PKey::RSA)
657 expect(verified_token_signature[1]).to eq('RS256')
658 end
659 end
660
661 private
662
663 def make_jwt_validator(opt_domain: domain, opt_issuer: nil)
664 opts = OpenStruct.new(
665 domain: opt_domain,
666 client_id: client_id,
667 client_secret: client_secret
668 )
669 opts[:issuer] = opt_issuer unless opt_issuer.nil?
670
671 OmniAuth::Auth0::JWTValidator.new(opts)
672 end
673
674 def make_hs256_token(payload = nil, secret = nil)
675 payload = { sub: 'abc123' } if payload.nil?
676 secret = client_secret if secret.nil?
677 JWT.encode payload, secret, 'HS256'
678 end
679
680 def make_rs256_token(payload = nil, kid = nil)
681 payload = { sub: 'abc123' } if payload.nil?
682 kid = valid_jwks_kid if kid.nil?
683 JWT.encode payload, rsa_private_key, 'RS256', kid: kid
684 end
685
686 def make_cert(private_key)
687 cert = OpenSSL::X509::Certificate.new
688 cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0')
689 cert.subject = cert.issuer
690 cert.not_before = Time.now
691 cert.not_after = Time.now + 365 * 24 * 60 * 60
692 cert.public_key = private_key.public_key
693 cert.serial = 0x0
694 cert.version = 2
695
696 ef = OpenSSL::X509::ExtensionFactory.new
697 ef.subject_certificate = cert
698 ef.issuer_certificate = cert
699 cert.extensions = [
700 ef.create_extension('basicConstraints', 'CA:TRUE', true),
701 ef.create_extension('subjectKeyIdentifier', 'hash')
702 ]
703 cert.add_extension ef.create_extension(
704 'authorityKeyIdentifier',
705 'keyid:always,issuer:always'
706 )
707
708 cert.sign private_key, OpenSSL::Digest::SHA1.new
709 end
710
711 def stub_complete_jwks
712 stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json')
713 .to_return(
714 headers: { 'Content-Type' => 'application/json' },
715 body: jwks.to_json,
716 status: 200
717 )
718 end
719
720 def stub_expected_jwks
721 stub_request(:get, 'https://example.org/.well-known/jwks.json')
722 .to_return(
723 headers: { 'Content-Type' => 'application/json' },
724 body: valid_jwks,
725 status: 200
726 )
727 end
728 end
0 require 'spec_helper'
1 require 'json'
2
3 describe OmniAuth::Auth0::Telemetry do
4
5 let(:test_class) { Class.new.extend(OmniAuth::Auth0::Telemetry) }
6
7 describe 'telemetry' do
8
9 it 'should have the correct SDK name' do
10 expect(test_class.telemetry).to have_key(:name)
11 expect(test_class.telemetry[:name]).to eq('omniauth-auth0')
12 end
13
14 it 'should have the correct SDK version' do
15 expect(test_class.telemetry).to have_key(:version)
16 expect(test_class.telemetry[:version]).to eq(OmniAuth::Auth0::VERSION)
17 end
18
19 it 'should include the Ruby version' do
20 expect(test_class.telemetry).to have_key(:env)
21 expect(test_class.telemetry[:env]).to have_key(:ruby)
22 expect(test_class.telemetry[:env][:ruby]).to eq(RUBY_VERSION)
23 end
24
25 end
26
27 end
0 # frozen_string_literal: true
1
02 require 'spec_helper'
3 require 'jwt'
14
25 RSpec.shared_examples 'site has valid domain url' do |url|
36 it { expect(subject.site).to eq(url) }
2225 end
2326
2427 describe 'client_options' do
25 let(:subject) { auth0.client }
28 let(:subject) { OmniAuth::Strategies::Auth0.new(
29 application,
30 client_id,
31 client_secret,
32 domain_url
33 ).client }
2634
2735 context 'domain with https' do
2836 let(:domain_url) { 'https://samples.auth0.com' }
7785 expect(redirect_url).to have_query('state')
7886 expect(redirect_url).to have_query('client_id')
7987 expect(redirect_url).to have_query('redirect_uri')
88 expect(redirect_url).not_to have_query('auth0Client')
89 expect(redirect_url).not_to have_query('connection')
90 expect(redirect_url).not_to have_query('connection_scope')
91 expect(redirect_url).not_to have_query('prompt')
92 expect(redirect_url).not_to have_query('screen_hint')
93 expect(redirect_url).not_to have_query('login_hint')
94 expect(redirect_url).not_to have_query('organization')
95 expect(redirect_url).not_to have_query('invitation')
96 end
97
98 it 'redirects to hosted login page' do
99 get 'auth/auth0?connection=abcd'
100 expect(last_response.status).to eq(302)
101 redirect_url = last_response.headers['Location']
102 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
103 expect(redirect_url).to have_query('response_type', 'code')
104 expect(redirect_url).to have_query('state')
105 expect(redirect_url).to have_query('client_id')
106 expect(redirect_url).to have_query('redirect_uri')
107 expect(redirect_url).to have_query('connection', 'abcd')
108 expect(redirect_url).not_to have_query('auth0Client')
109 expect(redirect_url).not_to have_query('connection_scope')
110 expect(redirect_url).not_to have_query('prompt')
111 expect(redirect_url).not_to have_query('screen_hint')
112 expect(redirect_url).not_to have_query('login_hint')
113 expect(redirect_url).not_to have_query('organization')
114 expect(redirect_url).not_to have_query('invitation')
115 end
116
117 it 'redirects to the hosted login page with connection_scope' do
118 get 'auth/auth0?connection_scope=identity_provider_scope'
119 expect(last_response.status).to eq(302)
120 redirect_url = last_response.headers['Location']
121 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
122 expect(redirect_url)
123 .to have_query('connection_scope', 'identity_provider_scope')
124 end
125
126 it 'redirects to hosted login page with prompt=login' do
127 get 'auth/auth0?prompt=login'
128 expect(last_response.status).to eq(302)
129 redirect_url = last_response.headers['Location']
130 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
131 expect(redirect_url).to have_query('response_type', 'code')
132 expect(redirect_url).to have_query('state')
133 expect(redirect_url).to have_query('client_id')
134 expect(redirect_url).to have_query('redirect_uri')
135 expect(redirect_url).to have_query('prompt', 'login')
136 expect(redirect_url).not_to have_query('auth0Client')
137 expect(redirect_url).not_to have_query('connection')
138 expect(redirect_url).not_to have_query('login_hint')
139 expect(redirect_url).not_to have_query('organization')
140 expect(redirect_url).not_to have_query('invitation')
141 end
142
143 it 'redirects to hosted login page with screen_hint=signup' do
144 get 'auth/auth0?screen_hint=signup'
145 expect(last_response.status).to eq(302)
146 redirect_url = last_response.headers['Location']
147 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
148 expect(redirect_url).to have_query('response_type', 'code')
149 expect(redirect_url).to have_query('state')
150 expect(redirect_url).to have_query('client_id')
151 expect(redirect_url).to have_query('redirect_uri')
152 expect(redirect_url).to have_query('screen_hint', 'signup')
153 expect(redirect_url).not_to have_query('auth0Client')
154 expect(redirect_url).not_to have_query('connection')
155 expect(redirect_url).not_to have_query('login_hint')
156 expect(redirect_url).not_to have_query('organization')
157 expect(redirect_url).not_to have_query('invitation')
158 end
159
160 it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do
161 get 'auth/auth0?organization=TestOrg&invitation=TestInvite'
162 expect(last_response.status).to eq(302)
163 redirect_url = last_response.headers['Location']
164 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
165 expect(redirect_url).to have_query('response_type', 'code')
166 expect(redirect_url).to have_query('state')
167 expect(redirect_url).to have_query('client_id')
168 expect(redirect_url).to have_query('redirect_uri')
169 expect(redirect_url).to have_query('organization', 'TestOrg')
170 expect(redirect_url).to have_query('invitation', 'TestInvite')
171 expect(redirect_url).not_to have_query('auth0Client')
172 expect(redirect_url).not_to have_query('connection')
173 expect(redirect_url).not_to have_query('connection_scope')
174 expect(redirect_url).not_to have_query('prompt')
175 expect(redirect_url).not_to have_query('screen_hint')
176 expect(redirect_url).not_to have_query('login_hint')
177 end
178
179 it 'redirects to hosted login page with login_hint=example@mail.com' do
180 get 'auth/auth0?login_hint=example@mail.com'
181 expect(last_response.status).to eq(302)
182 redirect_url = last_response.headers['Location']
183 expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
184 expect(redirect_url).to have_query('response_type', 'code')
185 expect(redirect_url).to have_query('state')
186 expect(redirect_url).to have_query('client_id')
187 expect(redirect_url).to have_query('redirect_uri')
188 expect(redirect_url).to have_query('login_hint', 'example@mail.com')
189 expect(redirect_url).not_to have_query('auth0Client')
190 expect(redirect_url).not_to have_query('connection')
191 expect(redirect_url).not_to have_query('connection_scope')
192 expect(redirect_url).not_to have_query('prompt')
193 expect(redirect_url).not_to have_query('screen_hint')
194 expect(redirect_url).not_to have_query('organization')
195 expect(redirect_url).not_to have_query('invitation')
80196 end
81197
82198 describe 'callback' do
84200 let(:expires_in) { 2000 }
85201 let(:token_type) { 'bearer' }
86202 let(:refresh_token) { 'refresh token' }
87 let(:id_token) { 'id token' }
203 let(:telemetry_value) { Class.new.extend(OmniAuth::Auth0::Telemetry).telemetry_encoded }
88204
89205 let(:user_id) { 'user identifier' }
90206 let(:state) { SecureRandom.hex(8) }
94210 let(:email) { 'mail@mail.com' }
95211 let(:email_verified) { true }
96212
213 let(:id_token) do
214 payload = {}
215 payload['sub'] = user_id
216 payload['iss'] = "#{domain_url}/"
217 payload['aud'] = client_id
218 payload['name'] = name
219 payload['nickname'] = nickname
220 payload['picture'] = picture
221 payload['email'] = email
222 payload['email_verified'] = email_verified
223
224 JWT.encode payload, client_secret, 'HS256'
225 end
226
97227 let(:oauth_response) do
98228 {
99229 access_token: access_token,
111241 }
112242 end
113243
114 let(:basic_user_info) { { sub: user_id } }
115 let(:oidc_user_info) do
116 {
117 sub: user_id,
118 name: name,
119 nickname: nickname,
120 email: email,
121 picture: picture,
122 email_verified: email_verified
123 }
124 end
244 let(:basic_user_info) { { "sub" => user_id, "name" => name } }
125245
126246 def stub_auth(body)
127247 stub_request(:post, 'https://samples.auth0.com/oauth/token')
248 .with(headers: { 'Auth0-Client' => telemetry_value })
128249 .to_return(
129250 headers: { 'Content-Type' => 'application/json' },
130251 body: MultiJson.encode(body)
148269 WebMock.reset!
149270 end
150271
151 let(:subject) { MultiJson.decode(last_response.body) }
272 let(:subject) do
273 MultiJson.decode(last_response.body)
274 end
152275
153276 context 'basic oauth' do
154277 before do
167290 expect(subject['credentials']['expires_at']).to_not be_nil
168291 end
169292
170 it 'has basic values' do
293 it 'has basic values' do
171294 expect(subject['provider']).to eq('auth0')
172295 expect(subject['uid']).to eq(user_id)
173 expect(subject['info']['name']).to eq(user_id)
296 expect(subject['info']['name']).to eq(name)
297 end
298
299 it 'should use the user info endpoint' do
300 expect(subject['extra']['raw_info']).to eq(basic_user_info)
174301 end
175302 end
176303
196323 context 'oidc' do
197324 before do
198325 stub_auth(oidc_response)
199 stub_userinfo(oidc_user_info)
200326 trigger_callback
201327 end
202328
272398 uri = redirect_uri(actual)
273399 query = query(uri)
274400 if value.nil?
275 query[key].length == 1
401 query.key?(key)
276402 else
277403 query[key] == [value]
278404 end
0 {
1 "keys": [
2 {
3 "alg": "RS256",
4 "kty": "RSA",
5 "use": "sig",
6 "x5c": [
7 "MIIDCzCCAfOgAwIBAgIJAJP6qydiMpsuMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNVBAMMEXNhbXBsZXMuYXV0aDAuY29tMB4XDTE0MDUyNjIyMDA1MFoXDTI4MDIwMjIyMDA1MFowHDEaMBgGA1UEAwwRc2FtcGxlcy5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkH4CFGSJ4s3mwCBzaGGwxa9Jxzfb1ia4nUumxbsuaB7PClZZgrNQiOR3MXVNV9W6F1D+wjT6oFHOo7TOkVI22I/ff3XZTE0F35UUHGWRtiQ4LdZxwOPTed2Lax3F2DEyl3Y0CguUKbq2sSghvHYcggM6aj3N53VBsnBh/kdrURDLx1RYqBIL6Fvkhb/V/v/u9UKhZM0CDQRef9FZ7R8q9ie9cnbDOj1dT9d64kiJIYtTraG0gOrs4LI+4KK0EZu5R7Uo053IK7kfNasWhDkl8yxNYkDxwfcIuAcDmLgLnAI4tfW5beJuw+/w75PO/EwzwsnvppXaAz7e3Wf8g1yWFAgMBAAGjUDBOMB0GA1UdDgQWBBTsmytFLNox+NUZdTNlCUL3hHrngTAfBgNVHSMEGDAWgBTsmytFLNox+NUZdTNlCUL3hHrngTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAodbRX/34LnWB70l8dpDF1neDoG29F0XdpE9ICWHeWB1gb/FvJ5UMy9/pnL0DI3mPwkTDDob+16Zc68o6dT6sH3vEUP1iRreJlFADEmJZjrH9P4Y7ttx3G2Uw2RU5uucXIqiyMDBrQo4vx4Lnghl+b/WYbZJgzLfZLgkOEjcznS0Yi5Wdz6MvaL3FehSfweHyrjmxz0e8elHq7VY8OqRA+4PmUBce9BgDCk9fZFjgj8l0m9Vc5pPKSY9LMmTyrYkeDr/KppqdXKOCHmv7AIGb6rMCtbkIL/CM7Bh9Hx78/UKAz87Sl9A1yXVNjKbZwOEW60ORIwJmd8Tv46gJF+/rV"
8 ],
9 "n": "pB-AhRkieLN5sAgc2hhsMWvScc329YmuJ1LpsW7LmgezwpWWYKzUIjkdzF1TVfVuhdQ_sI0-qBRzqO0zpFSNtiP33912UxNBd-VFBxlkbYkOC3WccDj03ndi2sdxdgxMpd2NAoLlCm6trEoIbx2HIIDOmo9zed1QbJwYf5Ha1EQy8dUWKgSC-hb5IW_1f7_7vVCoWTNAg0EXn_RWe0fKvYnvXJ2wzo9XU_XeuJIiSGLU62htIDq7OCyPuCitBGbuUe1KNOdyCu5HzWrFoQ5JfMsTWJA8cH3CLgHA5i4C5wCOLX1uW3ibsPv8O-TzvxMM8LJ76aV2gM-3t1n_INclhQ",
10 "e": "AQAB",
11 "kid": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg",
12 "x5t": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg"
13 },
14 {
15 "alg": "RS256",
16 "kty": "RSA",
17 "use": "sig",
18 "x5c": [
19 "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"
20 ],
21 "n": "z6KOQwP1_LLgGbo3Zg9g_PBoLbvBv5Li1HRLo9Vt_sM5hXBKkKROd9q4PC9LqaVigG5i1s5LsMoqDQ4VsArV57oNwRUH-S63Op08Y5OGULfx3bGFghkkmbnjFBv9yPtj0qpd2rQ9p3nY2OmfYVlPJMzoxfcM5CzID3_zmKbs_QPunfvyvIwKUT37ENSw1VgxvGEbY_iMh4Ur10vnCtN4rg-8lSypCGbqEvMpjCaZ2TjRxnbP9z_aR2NvSqpYkmFtHhhF9wSGq2RtOoESfvPFlG0Yvfq31QKBi6vvkvm3NNtbcSvqmEt55J4WNLr_zgwW-HnsnLg443ciwQkoVb21xQ",
22 "e": "AQAB",
23 "kid": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg",
24 "x5t": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg"
25 }
26 ]
27 }
0 $LOAD_PATH.unshift File.expand_path('..', __FILE__)
1 $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
0 $LOAD_PATH.unshift File.expand_path(__dir__)
1 $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
22
33 require 'simplecov'
4 if ENV['COVERAGE']
5 SimpleCov.start do
6 minimum_coverage(89.8)
7 end
4 SimpleCov.start
5
6 if ENV['CI'] == 'true'
7 require 'codecov'
8 SimpleCov.formatter = SimpleCov::Formatter::Codecov
89 end
10
911 require 'rspec'
1012 require 'rack/test'
1113 require 'webmock/rspec'