omniauth-auth0-3.1.0/ 0000755 0000041 0000041 00000000000 14350575705 014421 5 ustar www-data www-data omniauth-auth0-3.1.0/Gemfile.lock 0000644 0000041 0000041 00000010024 14350575705 016640 0 ustar www-data www-data PATH remote: . specs: omniauth-auth0 (3.1.0) omniauth (~> 2) omniauth-oauth2 (~> 1) GEM remote: https://rubygems.org/ specs: addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) coderay (1.1.3) crack (0.4.5) rexml daemons (1.4.1) diff-lcs (1.5.0) docile (1.4.0) dotenv (2.8.1) eventmachine (1.2.7) faraday (2.7.1) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) ffi (1.15.5) formatador (1.1.0) gem-release (2.2.2) guard (2.18.0) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) pry (>= 0.13.0) shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) guard-rspec (4.7.3) guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) hashie (5.0.0) json (2.6.3) jwt (2.5.0) listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) lumberjack (1.2.8) method_source (1.0.0) multi_json (1.15.0) multi_xml (0.6.0) mustermann (2.0.2) ruby2_keywords (~> 0.0.1) nenv (0.3.0) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) multi_xml (~> 0.5) rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) omniauth (2.1.0) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection omniauth-oauth2 (1.8.0) oauth2 (>= 1.4, < 3) omniauth (~> 2.0) parallel (1.22.1) parser (3.1.3.0) ast (~> 2.4.1) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.0.0) rack (2.2.4) rack-protection (2.2.3) rack rack-test (2.0.2) rack (>= 1.3) rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (2.6.1) rexml (3.2.5) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) rspec-mocks (~> 3.12.0) rspec-core (3.12.0) rspec-support (~> 3.12.0) rspec-expectations (3.12.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-mocks (3.12.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-support (3.12.0) rubocop (1.39.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.1.2.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.23.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.24.0) parser (>= 3.1.1.0) ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) shellany (0.0.1) shotgun (0.9.2) rack (>= 1.0) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-cobertura (2.1.0) rexml simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) sinatra (2.2.3) mustermann (~> 2.0) rack (~> 2.2) rack-protection (= 2.2.3) tilt (~> 2.0) snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) thin (1.8.1) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) thor (1.2.1) tilt (2.0.11) unicode-display_width (2.3.0) version_gem (1.1.1) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) PLATFORMS arm64-darwin-21 x86_64-darwin-20 x86_64-darwin-21 x86_64-linux DEPENDENCIES bundler dotenv (~> 2) gem-release (~> 2) guard-rspec (~> 4) jwt (~> 2) listen (~> 3) multi_json (~> 1) omniauth-auth0! pry (~> 0) rack-test (~> 2) rake (~> 13) rspec (~> 3) rubocop (~> 1) shotgun (~> 0) simplecov-cobertura (~> 2) sinatra (~> 2) thin (~> 1) webmock (~> 3) BUNDLED WITH 2.3.7 omniauth-auth0-3.1.0/.devcontainer/ 0000755 0000041 0000041 00000000000 14350575705 017160 5 ustar www-data www-data omniauth-auth0-3.1.0/.devcontainer/devcontainer.json 0000644 0000041 0000041 00000001035 14350575705 022533 0 ustar www-data www-data { "name": "Ruby", "image": "mcr.microsoft.com/devcontainers/ruby:3.1", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "lts" } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "ruby --version", // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" } omniauth-auth0-3.1.0/.rspec 0000644 0000041 0000041 00000000057 14350575705 015540 0 ustar www-data www-data --color --require spec_helper --format=progress omniauth-auth0-3.1.0/README.md 0000644 0000041 0000041 00000013524 14350575705 015705 0 ustar www-data www-data  [](https://circleci.com/gh/auth0/omniauth-auth0) [](https://codecov.io/gh/auth0/omniauth-auth0) [](https://badge.fury.io/rb/omniauth-auth0) [](https://github.com/auth0/omniauth-auth0/blob/master/LICENSE)
## Documentation - [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails) - [Sample projects](https://github.com/auth0-samples/auth0-rubyonrails-sample) - [API Reference](https://www.rubydoc.info/gems/omniauth-auth0) ## Getting started ### Installation Add the following line to your `Gemfile`: ```ruby gem 'omniauth-auth0' ``` If you're using this strategy with Rails, also add the following for CSRF protection: ```ruby gem 'omniauth-rails_csrf_protection' ``` Then install: ```bash $ bundle install ``` See our [contributing guide](CONTRIBUTING.md) for information on local installation for development. ## Configure the SDK Adding the SDK to your Rails app requires a few steps: - [Create the configuration file](#create-the-configuration-file) - [Create the initializer](#create-the-initializer) - [Create the callback controller](#create-the-callback-controller) - [Add routes](#add-routes) ### Create the configuration file Create the file `./config/auth0.yml` within your application directory with the following content: ```yml development: auth0_domain:
Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?
This project is licensed under the MIT license. See the LICENSE file for more info.
omniauth-auth0-3.1.0/.circleci/ 0000755 0000041 0000041 00000000000 14350575705 016254 5 ustar www-data www-data omniauth-auth0-3.1.0/.circleci/config.yml 0000644 0000041 0000041 00000002725 14350575705 020252 0 ustar www-data www-data version: 2.1 orbs: ship: auth0/ship@0 codecov: codecov/codecov@3 matrix_rubyversions: &matrix_rubyversions matrix: parameters: rubyversion: ["2.7", "3.0", "3.1"] # Default version of ruby to use for lint and publishing default_rubyversion: &default_rubyversion "2.7" executors: ruby: parameters: rubyversion: type: string default: *default_rubyversion docker: - image: cimg/ruby:<< parameters.rubyversion >> jobs: run-tests: parameters: rubyversion: type: string default: *default_rubyversion executor: name: ruby rubyversion: "<< parameters.rubyversion >>" steps: - checkout - restore_cache: keys: - gems-v2-{{ checksum "Gemfile" }} - gems-v2- - run: | echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV source $BASH_ENV gem install bundler bundle check || bundle install - save_cache: key: gems-v2--{{ checksum "Gemfile" }} paths: - vendor/bundle - run: bundle exec rake spec - codecov/upload workflows: tests: jobs: - run-tests: <<: *matrix_rubyversions - ship/ruby-publish: context: - publish-rubygems - publish-gh filters: branches: only: - master requires: - run-tests omniauth-auth0-3.1.0/spec/ 0000755 0000041 0000041 00000000000 14350575705 015353 5 ustar www-data www-data omniauth-auth0-3.1.0/spec/omniauth/ 0000755 0000041 0000041 00000000000 14350575705 017177 5 ustar www-data www-data omniauth-auth0-3.1.0/spec/omniauth/strategies/ 0000755 0000041 0000041 00000000000 14350575705 021351 5 ustar www-data www-data omniauth-auth0-3.1.0/spec/omniauth/strategies/auth0_spec.rb 0000644 0000041 0000041 00000035264 14350575705 023743 0 ustar www-data www-data # frozen_string_literal: true require 'spec_helper' require 'jwt' require 'multi_json' OmniAuth.config.allowed_request_methods = [:get, :post] RSpec.shared_examples 'site has valid domain url' do |url| it { expect(subject.site).to eq(url) } end describe OmniAuth::Strategies::Auth0 do let(:client_id) { 'CLIENT_ID' } let(:client_secret) { 'CLIENT_SECRET' } let(:domain_url) { 'https://samples.auth0.com' } let(:application) do lambda do [200, {}, ['Hello.']] end end let(:auth0) do OmniAuth::Strategies::Auth0.new( application, client_id, client_secret, domain_url ) end describe 'client_options' do let(:subject) { OmniAuth::Strategies::Auth0.new( application, client_id, client_secret, domain_url ).client } context 'domain with https' do let(:domain_url) { 'https://samples.auth0.com' } it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' end context 'domain with http' do let(:domain_url) { 'http://mydomain.com' } it_behaves_like 'site has valid domain url', 'http://mydomain.com' end context 'domain with host only' do let(:domain_url) { 'samples.auth0.com' } it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' end it 'should have correct authorize path' do expect(subject.options[:authorize_url]).to eq('/authorize') end it 'should have the correct userinfo path' do expect(subject.options[:userinfo_url]).to eq('/userinfo') end it 'should have the correct token path' do expect(subject.options[:token_url]).to eq('/oauth/token') end end describe 'options' do let(:subject) { auth0.options } it 'should have the correct client_id' do expect(subject[:client_id]).to eq(client_id) end it 'should have the correct client secret' do expect(subject[:client_secret]).to eq(client_secret) end it 'should have correct domain' do expect(subject[:domain]).to eq(domain_url) end end describe 'oauth' do it 'redirects to hosted login page' do get 'auth/auth0' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection') expect(redirect_url).not_to have_query('connection_scope') expect(redirect_url).not_to have_query('prompt') expect(redirect_url).not_to have_query('screen_hint') expect(redirect_url).not_to have_query('login_hint') expect(redirect_url).not_to have_query('organization') expect(redirect_url).not_to have_query('invitation') end it 'redirects to hosted login page' do get 'auth/auth0?connection=abcd' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).to have_query('connection', 'abcd') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection_scope') expect(redirect_url).not_to have_query('prompt') expect(redirect_url).not_to have_query('screen_hint') expect(redirect_url).not_to have_query('login_hint') expect(redirect_url).not_to have_query('organization') expect(redirect_url).not_to have_query('invitation') end it 'redirects to the hosted login page with connection_scope' do get 'auth/auth0?connection_scope=identity_provider_scope' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url) .to have_query('connection_scope', 'identity_provider_scope') end it 'redirects to hosted login page with prompt=login' do get 'auth/auth0?prompt=login' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).to have_query('prompt', 'login') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection') expect(redirect_url).not_to have_query('login_hint') expect(redirect_url).not_to have_query('organization') expect(redirect_url).not_to have_query('invitation') end it 'redirects to hosted login page with screen_hint=signup' do get 'auth/auth0?screen_hint=signup' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).to have_query('screen_hint', 'signup') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection') expect(redirect_url).not_to have_query('login_hint') expect(redirect_url).not_to have_query('organization') expect(redirect_url).not_to have_query('invitation') end it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do get 'auth/auth0?organization=TestOrg&invitation=TestInvite' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).to have_query('organization', 'TestOrg') expect(redirect_url).to have_query('invitation', 'TestInvite') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection') expect(redirect_url).not_to have_query('connection_scope') expect(redirect_url).not_to have_query('prompt') expect(redirect_url).not_to have_query('screen_hint') expect(redirect_url).not_to have_query('login_hint') end it 'redirects to hosted login page with login_hint=example@mail.com' do get 'auth/auth0?login_hint=example@mail.com' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to start_with('https://samples.auth0.com/authorize') expect(redirect_url).to have_query('response_type', 'code') expect(redirect_url).to have_query('state') expect(redirect_url).to have_query('client_id') expect(redirect_url).to have_query('redirect_uri') expect(redirect_url).to have_query('login_hint', 'example@mail.com') expect(redirect_url).not_to have_query('auth0Client') expect(redirect_url).not_to have_query('connection') expect(redirect_url).not_to have_query('connection_scope') expect(redirect_url).not_to have_query('prompt') expect(redirect_url).not_to have_query('screen_hint') expect(redirect_url).not_to have_query('organization') expect(redirect_url).not_to have_query('invitation') end def session session_cookie = last_response.cookies['rack.session'].first session_data, _, _ = session_cookie.rpartition('--') decoded_session_data = Base64.decode64(session_data) Marshal.load(decoded_session_data) end it "stores session['authorize_params'] as a plain Ruby Hash" do get '/auth/auth0' expect(session['authorize_params'].class).to eq(::Hash) end describe 'callback' do let(:access_token) { 'access token' } let(:expires_in) { 2000 } let(:token_type) { 'bearer' } let(:refresh_token) { 'refresh token' } let(:telemetry_value) { Class.new.extend(OmniAuth::Auth0::Telemetry).telemetry_encoded } let(:user_id) { 'user identifier' } let(:state) { SecureRandom.hex(8) } let(:name) { 'John' } let(:nickname) { 'J' } let(:picture) { 'some picture url' } let(:email) { 'mail@mail.com' } let(:email_verified) { true } let(:id_token) do payload = {} payload['sub'] = user_id payload['iss'] = "#{domain_url}/" payload['aud'] = client_id payload['name'] = name payload['nickname'] = nickname payload['picture'] = picture payload['email'] = email payload['email_verified'] = email_verified JWT.encode payload, client_secret, 'HS256' end let(:oauth_response) do { access_token: access_token, expires_in: expires_in, token_type: token_type } end let(:oidc_response) do { id_token: id_token, access_token: access_token, expires_in: expires_in, token_type: token_type } end let(:basic_user_info) { { "sub" => user_id, "name" => name } } def stub_auth(body) stub_request(:post, 'https://samples.auth0.com/oauth/token') .with(headers: { 'Auth0-Client' => telemetry_value }) .to_return( headers: { 'Content-Type' => 'application/json' }, body: MultiJson.encode(body) ) end def stub_userinfo(body) stub_request(:get, 'https://samples.auth0.com/userinfo') .to_return( headers: { 'Content-Type' => 'application/json' }, body: MultiJson.encode(body) ) end def trigger_callback get '/auth/auth0/callback', { 'state' => state }, 'rack.session' => { 'omniauth.state' => state } end before(:each) do WebMock.reset! end let(:subject) do MultiJson.decode(last_response.body) end context 'basic oauth' do before do stub_auth(oauth_response) stub_userinfo(basic_user_info) trigger_callback end it 'to succeed' do expect(last_response.status).to eq(200) end it 'has credentials' do expect(subject['credentials']['token']).to eq(access_token) expect(subject['credentials']['expires']).to be true expect(subject['credentials']['expires_at']).to_not be_nil end it 'has basic values' do expect(subject['provider']).to eq('auth0') expect(subject['uid']).to eq(user_id) expect(subject['info']['name']).to eq(name) end it 'should use the user info endpoint' do expect(subject['extra']['raw_info']).to eq(basic_user_info) end end context 'basic oauth w/refresh token' do before do stub_auth(oauth_response.merge(refresh_token: refresh_token)) stub_userinfo(basic_user_info) trigger_callback end it 'to succeed' do expect(last_response.status).to eq(200) end it 'has credentials' do expect(subject['credentials']['token']).to eq(access_token) expect(subject['credentials']['refresh_token']).to eq(refresh_token) expect(subject['credentials']['expires']).to be true expect(subject['credentials']['expires_at']).to_not be_nil end end context 'oidc' do before do stub_auth(oidc_response) trigger_callback end it 'to succeed' do expect(last_response.status).to eq(200) end it 'has credentials' do expect(subject['credentials']['token']).to eq(access_token) expect(subject['credentials']['expires']).to be true expect(subject['credentials']['expires_at']).to_not be_nil expect(subject['credentials']['id_token']).to eq(id_token) end it 'has basic values' do expect(subject['provider']).to eq('auth0') expect(subject['uid']).to eq(user_id) end it 'has info' do expect(subject['info']['name']).to eq(name) expect(subject['info']['nickname']).to eq(nickname) expect(subject['info']['image']).to eq(picture) expect(subject['info']['email']).to eq(email) end it 'has extra' do expect(subject['extra']['raw_info']['email_verified']).to be true end end end end describe 'error_handling' do it 'fails when missing client_id' do @app = make_application(client_id: nil) get 'auth/auth0' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to fail_auth_with('missing_client_id') end it 'fails when missing client_secret' do @app = make_application(client_secret: nil) get 'auth/auth0' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to fail_auth_with('missing_client_secret') end it 'fails when missing domain' do @app = make_application(domain: nil) get 'auth/auth0' expect(last_response.status).to eq(302) redirect_url = last_response.headers['Location'] expect(redirect_url).to fail_auth_with('missing_domain') end end end RSpec::Matchers.define :fail_auth_with do |message| match do |actual| uri = URI(actual) query = CGI.parse(uri.query) (uri.path == '/auth/failure') && (query['message'] == [message]) && (query['strategy'] == ['auth0']) end end RSpec::Matchers.define :have_query do |key, value| match do |actual| uri = redirect_uri(actual) query = query(uri) if value.nil? query.key?(key) else query[key] == [value] end end def redirect_uri(string) URI(string) end def query(uri) CGI.parse(uri.query) end end omniauth-auth0-3.1.0/spec/omniauth/auth0/ 0000755 0000041 0000041 00000000000 14350575705 020220 5 ustar www-data www-data omniauth-auth0-3.1.0/spec/omniauth/auth0/telemetry_spec.rb 0000644 0000041 0000041 00000001420 14350575705 023566 0 ustar www-data www-data require 'spec_helper' require 'json' describe OmniAuth::Auth0::Telemetry do let(:test_class) { Class.new.extend(OmniAuth::Auth0::Telemetry) } describe 'telemetry' do it 'should have the correct SDK name' do expect(test_class.telemetry).to have_key(:name) expect(test_class.telemetry[:name]).to eq('omniauth-auth0') end it 'should have the correct SDK version' do expect(test_class.telemetry).to have_key(:version) expect(test_class.telemetry[:version]).to eq(OmniAuth::Auth0::VERSION) end it 'should include the Ruby version' do expect(test_class.telemetry).to have_key(:env) expect(test_class.telemetry[:env]).to have_key(:ruby) expect(test_class.telemetry[:env][:ruby]).to eq(RUBY_VERSION) end end end omniauth-auth0-3.1.0/spec/omniauth/auth0/jwt_validator_spec.rb 0000644 0000041 0000041 00000054705 14350575705 024443 0 ustar www-data www-data require 'spec_helper' require 'json' require 'jwt' describe OmniAuth::Auth0::JWTValidator do # # Reused data # let(:client_id) { 'CLIENT_ID' } let(:client_secret) { 'CLIENT_SECRET' } let(:domain) { 'samples.auth0.com' } let(:future_timecode) { 32_503_680_000 } let(:past_timecode) { 303_912_000 } let(:valid_jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' } let(:rsa_private_key) do OpenSSL::PKey::RSA.generate 2048 end let(:valid_jwks) do { keys: [ { kid: valid_jwks_kid, x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)] } ] }.to_json end let(:jwks) do current_dir = File.dirname(__FILE__) jwks_file = File.read("#{current_dir}/../../resources/jwks.json") JSON.parse(jwks_file, symbolize_names: true) end # # Specs # describe 'JWT verifier default values' do let(:jwt_validator) do make_jwt_validator end it 'should have the correct issuer' do expect(jwt_validator.issuer).to eq('https://samples.auth0.com/') end end describe 'JWT verifier token_head' do let(:jwt_validator) do make_jwt_validator end it 'should parse the head of a valid JWT' do expect(jwt_validator.token_head(make_hs256_token)[:alg]).to eq('HS256') end it 'should fail parsing the head of a blank JWT' do expect(jwt_validator.token_head('')).to eq({}) end it 'should fail parsing the head of an invalid JWT' do expect(jwt_validator.token_head('.')).to eq({}) end it 'should throw an exception for invalid JSON' do expect do jwt_validator.token_head('QXV0aDA=') end.to raise_error(JSON::ParserError) end end describe 'JWT verifier jwks_public_cert' do let(:jwt_validator) do make_jwt_validator end it 'should return a public_key' do x5c = jwks[:keys].first[:x5c].first public_cert = jwt_validator.jwks_public_cert(x5c) expect(public_cert.instance_of?(OpenSSL::PKey::RSA)).to eq(true) end it 'should fail with an invalid x5c' do expect do jwt_validator.jwks_public_cert('QXV0aDA=') end.to raise_error(OpenSSL::X509::CertificateError) end end describe 'JWT verifier jwks key parsing' do let(:jwt_validator) do make_jwt_validator end before do stub_complete_jwks end it 'should return a key' do expect(jwt_validator.jwks_key(:alg, valid_jwks_kid)).to eq('RS256') end it 'should return an x5c key' do expect(jwt_validator.jwks_key(:x5c, valid_jwks_kid).length).to eq(1) end it 'should return nil if there is not key' do expect(jwt_validator.jwks_key(:auth0, valid_jwks_kid)).to eq(nil) end it 'should return nil if the key ID is invalid' do expect(jwt_validator.jwks_key(:alg, "#{valid_jwks_kid}_invalid")).to eq(nil) end end describe 'JWT verifier custom issuer' do context 'same as domain' do let(:jwt_validator) do make_jwt_validator(opt_issuer: domain) end it 'should have the correct issuer' do expect(jwt_validator.issuer).to eq('https://samples.auth0.com/') end it 'should have the correct domain' do expect(jwt_validator.issuer).to eq('https://samples.auth0.com/') end end context 'different from domain' do shared_examples_for 'has correct issuer and domain' do let(:jwt_validator) { make_jwt_validator(opt_issuer: opt_issuer) } it 'should have the correct issuer' do expect(jwt_validator.issuer).to eq('https://different.auth0.com/') end it 'should have the correct domain' do expect(jwt_validator.domain).to eq('https://samples.auth0.com/') end end context 'without protocol and trailing slash' do let(:opt_issuer) { 'different.auth0.com' } it_behaves_like 'has correct issuer and domain' end context 'with protocol and trailing slash' do let(:opt_issuer) { 'https://different.auth0.com/' } it_behaves_like 'has correct issuer and domain' end end end describe 'JWT verifier verify' do let(:jwt_validator) do make_jwt_validator end before do stub_complete_jwks stub_expected_jwks end it 'should fail when JWT is nil' do expect do jwt_validator.verify(nil) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "ID token is required but missing" })) end it 'should fail when JWT is not well-formed' do expect do jwt_validator.verify('abc.123') end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "ID token could not be decoded" })) end it 'should fail with missing issuer' do expect do jwt_validator.verify(make_hs256_token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Issuer (iss) claim must be a string present in the ID token" })) end it 'should fail with invalid issuer' do payload = { iss: 'https://auth0.com/' } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Issuer (iss) claim mismatch in the ID token, expected (https://samples.auth0.com/), found (https://auth0.com/)" })) end it 'should fail when subject is missing' do payload = { iss: "https://#{domain}/", sub: '' } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Subject (sub) claim must be a string present in the ID token" })) end it 'should fail with missing audience' do payload = { iss: "https://#{domain}/", sub: 'sub' } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Audience (aud) claim must be a string or array of strings present in the ID token" })) end it 'should fail with invalid audience' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: 'Auth0' } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Audience (aud) claim mismatch in the ID token; expected #{client_id} but found Auth0" })) end it 'should fail when missing expiration' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Expiration time (exp) claim must be a number present in the ID token" })) end it 'should fail when past expiration' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(past_timecode + 60)})" })) end it 'should pass when past expiration but within default leeway' do exp = Time.now.to_i - 59 payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: exp, iat: past_timecode } token = make_hs256_token(payload) id_token = jwt_validator.verify(token) expect(id_token['exp']).to eq(exp) end it 'should fail when past expiration and outside default leeway' do exp = Time.now.to_i - 61 payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: exp, iat: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(exp + 60)})" })) end it 'should fail when missing iat' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Issued At (iat) claim must be a number present in the ID token" })) end it 'should fail when authorize params has nonce but nonce is missing in the token' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { nonce: 'noncey' }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Nonce (nonce) claim must be a string present in the ID token" })) end it 'should fail when authorize params has nonce but token nonce does not match' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode, nonce: 'mismatch' } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { nonce: 'noncey' }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Nonce (nonce) claim value mismatch in the ID token; expected (noncey), found (mismatch)" })) end it 'should fail when “aud” is an array of strings and azp claim is not present' do aud = [ client_id, "https://#{domain}/userinfo" ] payload = { iss: "https://#{domain}/", sub: 'sub', aud: aud, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values" })) end it 'should fail when "azp" claim doesnt match the expected aud' do aud = [ client_id, "https://#{domain}/userinfo" ] payload = { iss: "https://#{domain}/", sub: 'sub', aud: aud, exp: future_timecode, iat: past_timecode, azp: 'not_expected' } token = make_hs256_token(payload) expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Authorized Party (azp) claim mismatch in the ID token; expected (#{client_id}), found (not_expected)" })) end it 'should fail when “max_age” sent on the authentication request and this claim is not present' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { max_age: 60 }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified" })) end 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 payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode, auth_time: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { max_age: 60 }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ 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)})" })) end 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 now = Time.now.to_i auth_time = now - 121 max_age = 60 payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode, auth_time: auth_time } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { max_age: max_age }) # Time.at(auth_time + max_age + leeway end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ 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)})" })) end 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 now = Time.now.to_i auth_time = now - 119 max_age = 60 payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode, auth_time: auth_time } token = make_hs256_token(payload) id_token = jwt_validator.verify(token, { max_age: max_age }) expect(id_token['auth_time']).to eq(auth_time) end it 'should fail when authorize params has organization but org_id is missing in the token' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { organization: 'Test Org' }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Organization Id (org_id) claim must be a string present in the ID token" })) end it 'should fail when authorize params has organization but token org_id does not match' do payload = { iss: "https://#{domain}/", sub: 'sub', aud: client_id, exp: future_timecode, iat: past_timecode, org_id: 'Wrong Org' } token = make_hs256_token(payload) expect do jwt_validator.verify(token, { organization: 'Test Org' }) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Organization Id (org_id) claim value mismatch in the ID token; expected 'Test Org', found 'Wrong Org'" })) end it 'should fail for RS256 token when kid is incorrect' do domain = 'example.org' sub = 'abc123' payload = { sub: sub, exp: future_timecode, iss: "https://#{domain}/", iat: past_timecode, aud: client_id } invalid_kid = 'invalid-kid' token = make_rs256_token(payload, invalid_kid) expect do verified_token = make_jwt_validator(opt_domain: domain).verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Could not find a public key for Key ID (kid) 'invalid-kid'" })) end it 'should fail when RS256 token has invalid signature' do domain = 'example.org' sub = 'abc123' payload = { sub: sub, exp: future_timecode, iss: "https://#{domain}/", iat: past_timecode, aud: client_id } token = make_rs256_token(payload) + 'bad' expect do verified_token = make_jwt_validator(opt_domain: domain).verify(token) end.to raise_error(an_instance_of(JWT::VerificationError).and having_attributes({ message: "Signature verification failed" })) end it 'should fail when algorithm is not RS256 or HS256' do payload = { iss: "https://#{domain}/", sub: 'abc123', aud: client_id, exp: future_timecode, iat: past_timecode } token = JWT.encode payload, 'secret', 'HS384' expect do jwt_validator.verify(token) end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({ message: "Signature algorithm of HS384 is not supported. Expected the ID token to be signed with RS256 or HS256" })) end it 'should fail when HS256 token has invalid signature' do payload = { iss: "https://#{domain}/", sub: 'abc123', aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload, 'bad_secret') expect do # validator is configured to use "CLIENT_SECRET" by default jwt_validator.verify(token) end.to raise_error(an_instance_of(JWT::VerificationError)) end it 'should verify a valid HS256 token with multiple audiences' do audience = [ client_id, "https://#{domain}/userinfo" ] payload = { iss: "https://#{domain}/", sub: 'sub', aud: audience, exp: future_timecode, iat: past_timecode, azp: client_id } token = make_hs256_token(payload) id_token = jwt_validator.verify(token) expect(id_token['aud']).to eq(audience) end it 'should verify a standard HS256 token' do sub = 'abc123' payload = { iss: "https://#{domain}/", sub: sub, aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) verified_token = jwt_validator.verify(token) expect(verified_token['sub']).to eq(sub) end it 'should verify a standard RS256 token' do domain = 'example.org' sub = 'abc123' payload = { sub: sub, exp: future_timecode, iss: "https://#{domain}/", iat: past_timecode, aud: client_id } token = make_rs256_token(payload) verified_token = make_jwt_validator(opt_domain: domain).verify(token) expect(verified_token['sub']).to eq(sub) end it 'should verify a HS256 JWT signature when calling verify signature directly' do sub = 'abc123' payload = { iss: "https://#{domain}/", sub: sub, aud: client_id, exp: future_timecode, iat: past_timecode } token = make_hs256_token(payload) verified_token_signature = jwt_validator.verify_signature(token) expect(verified_token_signature[0]).to eq('CLIENT_SECRET') expect(verified_token_signature[1]).to eq('HS256') end it 'should verify a RS256 JWT signature verify signature directly' do domain = 'example.org' sub = 'abc123' payload = { sub: sub, exp: future_timecode, iss: "https://#{domain}/", iat: past_timecode, aud: client_id } token = make_rs256_token(payload) verified_token_signature = make_jwt_validator(opt_domain: domain).verify_signature(token) expect(verified_token_signature.length).to be(2) expect(verified_token_signature[0]).to be_a(OpenSSL::PKey::RSA) expect(verified_token_signature[1]).to eq('RS256') end end private def make_jwt_validator(opt_domain: domain, opt_issuer: nil) opts = OpenStruct.new( domain: opt_domain, client_id: client_id, client_secret: client_secret ) opts[:issuer] = opt_issuer unless opt_issuer.nil? OmniAuth::Auth0::JWTValidator.new(opts) end def make_hs256_token(payload = nil, secret = nil) payload = { sub: 'abc123' } if payload.nil? secret = client_secret if secret.nil? JWT.encode payload, secret, 'HS256' end def make_rs256_token(payload = nil, kid = nil) payload = { sub: 'abc123' } if payload.nil? kid = valid_jwks_kid if kid.nil? JWT.encode payload, rsa_private_key, 'RS256', kid: kid end def make_cert(private_key) cert = OpenSSL::X509::Certificate.new cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0') cert.subject = cert.issuer cert.not_before = Time.now cert.not_after = Time.now + 365 * 24 * 60 * 60 cert.public_key = private_key.public_key cert.serial = 0x0 cert.version = 2 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = cert cert.extensions = [ ef.create_extension('basicConstraints', 'CA:TRUE', true), ef.create_extension('subjectKeyIdentifier', 'hash') ] cert.add_extension ef.create_extension( 'authorityKeyIdentifier', 'keyid:always,issuer:always' ) cert.sign private_key, OpenSSL::Digest::SHA1.new end def stub_complete_jwks stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json') .to_return( headers: { 'Content-Type' => 'application/json' }, body: jwks.to_json, status: 200 ) end def stub_expected_jwks stub_request(:get, 'https://example.org/.well-known/jwks.json') .to_return( headers: { 'Content-Type' => 'application/json' }, body: valid_jwks, status: 200 ) end end omniauth-auth0-3.1.0/spec/resources/ 0000755 0000041 0000041 00000000000 14350575705 017365 5 ustar www-data www-data omniauth-auth0-3.1.0/spec/resources/jwks.json 0000644 0000041 0000041 00000006374 14350575705 021250 0 ustar www-data www-data { "keys": [ { "alg": "RS256", "kty": "RSA", "use": "sig", "x5c": [ "MIIDCzCCAfOgAwIBAgIJAJP6qydiMpsuMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNVBAMMEXNhbXBsZXMuYXV0aDAuY29tMB4XDTE0MDUyNjIyMDA1MFoXDTI4MDIwMjIyMDA1MFowHDEaMBgGA1UEAwwRc2FtcGxlcy5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkH4CFGSJ4s3mwCBzaGGwxa9Jxzfb1ia4nUumxbsuaB7PClZZgrNQiOR3MXVNV9W6F1D+wjT6oFHOo7TOkVI22I/ff3XZTE0F35UUHGWRtiQ4LdZxwOPTed2Lax3F2DEyl3Y0CguUKbq2sSghvHYcggM6aj3N53VBsnBh/kdrURDLx1RYqBIL6Fvkhb/V/v/u9UKhZM0CDQRef9FZ7R8q9ie9cnbDOj1dT9d64kiJIYtTraG0gOrs4LI+4KK0EZu5R7Uo053IK7kfNasWhDkl8yxNYkDxwfcIuAcDmLgLnAI4tfW5beJuw+/w75PO/EwzwsnvppXaAz7e3Wf8g1yWFAgMBAAGjUDBOMB0GA1UdDgQWBBTsmytFLNox+NUZdTNlCUL3hHrngTAfBgNVHSMEGDAWgBTsmytFLNox+NUZdTNlCUL3hHrngTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAodbRX/34LnWB70l8dpDF1neDoG29F0XdpE9ICWHeWB1gb/FvJ5UMy9/pnL0DI3mPwkTDDob+16Zc68o6dT6sH3vEUP1iRreJlFADEmJZjrH9P4Y7ttx3G2Uw2RU5uucXIqiyMDBrQo4vx4Lnghl+b/WYbZJgzLfZLgkOEjcznS0Yi5Wdz6MvaL3FehSfweHyrjmxz0e8elHq7VY8OqRA+4PmUBce9BgDCk9fZFjgj8l0m9Vc5pPKSY9LMmTyrYkeDr/KppqdXKOCHmv7AIGb6rMCtbkIL/CM7Bh9Hx78/UKAz87Sl9A1yXVNjKbZwOEW60ORIwJmd8Tv46gJF+/rV" ], "n": "pB-AhRkieLN5sAgc2hhsMWvScc329YmuJ1LpsW7LmgezwpWWYKzUIjkdzF1TVfVuhdQ_sI0-qBRzqO0zpFSNtiP33912UxNBd-VFBxlkbYkOC3WccDj03ndi2sdxdgxMpd2NAoLlCm6trEoIbx2HIIDOmo9zed1QbJwYf5Ha1EQy8dUWKgSC-hb5IW_1f7_7vVCoWTNAg0EXn_RWe0fKvYnvXJ2wzo9XU_XeuJIiSGLU62htIDq7OCyPuCitBGbuUe1KNOdyCu5HzWrFoQ5JfMsTWJA8cH3CLgHA5i4C5wCOLX1uW3ibsPv8O-TzvxMM8LJ76aV2gM-3t1n_INclhQ", "e": "AQAB", "kid": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg", "x5t": "NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg" }, { "alg": "RS256", "kty": "RSA", "use": "sig", "x5c": [ "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" ], "n": "z6KOQwP1_LLgGbo3Zg9g_PBoLbvBv5Li1HRLo9Vt_sM5hXBKkKROd9q4PC9LqaVigG5i1s5LsMoqDQ4VsArV57oNwRUH-S63Op08Y5OGULfx3bGFghkkmbnjFBv9yPtj0qpd2rQ9p3nY2OmfYVlPJMzoxfcM5CzID3_zmKbs_QPunfvyvIwKUT37ENSw1VgxvGEbY_iMh4Ur10vnCtN4rg-8lSypCGbqEvMpjCaZ2TjRxnbP9z_aR2NvSqpYkmFtHhhF9wSGq2RtOoESfvPFlG0Yvfq31QKBi6vvkvm3NNtbcSvqmEt55J4WNLr_zgwW-HnsnLg443ciwQkoVb21xQ", "e": "AQAB", "kid": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg", "x5t": "RUVBOTVEMEZBMTA5NDAzNEQzNTZGNzMyMTI4MzU1RkNFQzhCQTM0Mg" } ] } omniauth-auth0-3.1.0/spec/spec_helper.rb 0000644 0000041 0000041 00000002772 14350575705 020201 0 ustar www-data www-data $LOAD_PATH.unshift File.expand_path(__dir__) $LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'multi_json' require 'simplecov' SimpleCov.start if ENV['CI'] == 'true' require 'simplecov-cobertura' SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter end require 'rspec' require 'rack/test' require 'webmock/rspec' require 'omniauth' require 'omniauth-auth0' require 'sinatra' WebMock.disable_net_connect! RSpec.configure do |config| config.include WebMock::API config.include Rack::Test::Methods config.extend OmniAuth::Test::StrategyMacros, type: :strategy config.filter_run focus: true config.run_all_when_everything_filtered = true def app @app || make_application end def make_application(options = {}) client_id = 'CLIENT_ID' secret = 'CLIENT_SECRET' domain = 'samples.auth0.com' client_id = options.delete(:client_id) if options.key?(:client_id) secret = options.delete(:client_secret) if options.key?(:client_secret) domain = options.delete(:domain) if options.key?(:domain) Sinatra.new do configure do enable :sessions set :show_exceptions, false set :session_secret, '9771aff2c634257053c62ba072c54754bd2cc92739b37e81c3eda505da48c2ec' end use OmniAuth::Builder do provider :auth0, client_id, secret, domain, options end get '/auth/auth0/callback' do MultiJson.encode(env['omniauth.auth']) end end end end OmniAuth.config.logger = Logger.new('/dev/null') omniauth-auth0-3.1.0/EXAMPLES.md 0000644 0000041 0000041 00000013752 14350575705 016171 0 ustar www-data www-data * [Example of the resulting authentication hash](#example-of-the-resulting-authentication-hash) * [Send additional authentication parameters](#send-additional-authentication-parameters) * [Query Parameter Options](#query-parameter-options) * [Auth0 Organizations](#auth0-organizations) - [Logging in with an Organization](#logging-in-with-an-organization) - [Validating Organizations when using Organization Login Prompt](#validating-organizations-when-using-organization-login-prompt) - [Accepting user invitations](#accepting-user-invitations) ### Example of the resulting authentication hash The Auth0 strategy will provide the standard OmniAuth hash attributes: - `:provider` - the name of the strategy, in this case `auth0` - `:uid` - the user identifier - `:info` - the result of the call to `/userinfo` using OmniAuth standard attributes - `:credentials` - tokens requested and data - `:extra` - Additional info obtained from calling `/userinfo` in the `:raw_info` property ```ruby { :provider => 'auth0', :uid => 'auth0|USER_ID', :info => { :name => 'John Foo', :email => 'johnfoo@example.org', :nickname => 'john', :image => 'https://example.org/john.jpg' }, :credentials => { :token => 'ACCESS_TOKEN', :expires_at => 1485373937, :expires => true, :refresh_token => 'REFRESH_TOKEN', :id_token => 'JWT_ID_TOKEN', :token_type => 'bearer', }, :extra => { :raw_info => { :email => 'johnfoo@example.org', :email_verified => 'true', :name => 'John Foo', :picture => 'https://example.org/john.jpg', :user_id => 'auth0|USER_ID', :nickname => 'john', :created_at => '2014-07-15T17:19:50.387Z' } } } ``` ## Send additional authentication parameters To send additional parameters during login, you can specify them when you register the provider: ```ruby provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN'], { authorize_params: { scope: 'openid read:users write:order', audience: 'https://mydomain/api', max_age: 3600 # time in seconds authentication is valid } } ``` This will tell the strategy to send those parameters on every authentication request. ## Query Parameter Options In some scenarios, you may need to pass specific query parameters to `/authorize`. The following parameters are available to enable this: - `connection` - `connection_scope` - `prompt` - `screen_hint` (only relevant to New Universal Login Experience) - `organization` - `invitation` Simply pass these query parameters to your OmniAuth redirect endpoint to enable their behavior. ## Auth0 Organizations [Organizations](https://auth0.com/docs/organizations) is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications. Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans. ### Logging in with an Organization Logging in with an Organization is as easy as passing the parameters to the authorize endpoint. You can do this with ```ruby <%= button_to 'Login', 'auth/auth0', method: :post, params: { # Found in your Auth0 dashboard, under Organization settings: organization: '{AUTH0_ORGANIZATION}' } %> ``` Alternatively you can configure the organization when you register the provider: ```ruby provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN'] { authorize_params: { scope: 'openid read:users', audience: 'https://{AUTH0_DOMAIN}/api', organization: '{AUTH0_ORGANIZATION}' } } ``` When passing `openid` to the scope and `organization` to the authorize params, you will receive an ID token on callback with the `org_id` claim. This claim is validated for you by the SDK. ### Validating Organizations when using Organization Login Prompt When Organization login prompt is enabled on your application, but you haven't specified an Organization for the application's authorization endpoint, the `org_id` claim will be present on the ID token, and should be validated to ensure that the value received is expected or known. Normally, validating the issuer would be enough to ensure that the token was issued by Auth0, and this check is performed by the SDK. However, in the case of organizations, additional checks should be made so that the organization within an Auth0 tenant is expected. In particular, the `org_id` claim should be checked to ensure it is a value that is already known to the application. This could be validated against a known list of organization IDs, or perhaps checked in conjunction with the current request URL. e.g. the sub-domain may hint at what organization should be used to validate the ID Token. Here is an example using it in your `callback` method ```ruby def callback claims = request.env['omniauth.auth']['extra']['raw_info'] if claims["org"] && claims["org"] !== expected_org redirect_to '/unauthorized', status: 401 else session[:userinfo] = claims redirect_to '/dashboard' end end ``` For more information, please read [Work with Tokens and Organizations](https://auth0.com/docs/organizations/using-tokens) on Auth0 Docs. ### Accepting user invitations 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. 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. You can then supply those parametrs to a `button_to` or `link_to` helper ```ruby <%= button_to 'Login', 'auth/auth0', method: :post, params: { organization: '{YOUR_ORGANIZATION_ID}', invitation: '{INVITE_CODE}' } %> ``` omniauth-auth0-3.1.0/CHANGELOG.md 0000644 0000041 0000041 00000031312 14350575705 016232 0 ustar www-data www-data # Change Log ## [v3.1.0](https://github.com/auth0/omniauth-auth0/tree/v3.1.0) (2022-11-04) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.0.0...v3.1.0) **Added** - Add ui_locales to permitted params [\#135](https://github.com/auth0/omniauth-auth0/pull/135) ([martijn](https://github.com/martijn)) **Changed** - Store plain Hash in session['authorize_params'] [\#150](https://github.com/auth0/omniauth-auth0/pull/150) ([santry](https://github.com/santry)) - Redesign readme to match new style [\#148](https://github.com/auth0/omniauth-auth0/pull/148) ([stevehobbsdev](https://github.com/stevehobbsdev)) **Fixed** - Fix authentication hash link in code sample [\#153](https://github.com/auth0/omniauth-auth0/pull/153) ([ewanharris](https://github.com/ewanharris)) **Security** - [Snyk] Fix for 1 vulnerabilities [\#149](https://github.com/auth0/omniauth-auth0/pull/149) ([snyk-bot](https://github.com/snyk-bot)) - Bump addressable from 2.7.0 to 2.8.0 [\#133](https://github.com/auth0/omniauth-auth0/pull/133) ([dependabot[bot]](https://github.com/apps/dependabot)) - [Snyk] Security upgrade webmock from 3.12.2 to 3.12.2 [\#134](https://github.com/auth0/omniauth-auth0/pull/134) ([snyk-bot](https://github.com/snyk-bot)) ## [v3.0.0](https://github.com/auth0/omniauth-auth0/tree/v3.0.0) (2021-04-14) Version 3.0 introduces [Omniauth v2.0](https://github.com/omniauth/omniauth/releases/tag/v2.0.0) which addresses [CVE-2015-9284](https://nvd.nist.gov/vuln/detail/CVE-2015-9284). Omniauth now defaults to only allow `POST` as the allowed request_phase method. This was previously handled through the recommended [mitigation](https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284) using the `omniauth-rails_csrf_protection v0.x.x` gem to provide CSRF protection. ### Upgrading to omniauth-rails_csrf_protection v1.0.0 If you are using `omniauth-rails_csrf_protection` to provide CSRF protection, you will need to be upgrade to `1.x.x`. ### BREAKING CHANGES Now that OmniAuth now defaults to only `POST` as the allowed request_phase method, if you aren't already, you will need to convert any login links to use [form helpers](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for) with the `POST` method. ```html+ruby # OLD -- GET request Login # NEW Example #1 -- POST request <%= link_to 'Login', 'auth/auth0', method: :post %> # NEW Example #2 -- POST request <%= button_to 'Login', 'auth/auth0', method: :post %> # NEW Example #3 -- POST request <%= form_tag('/auth/auth0', method: :post) do %> <% end %> ``` ### Allowing GET Requests In the scenario you absolutely must use GET requests as an allowed request method for authentication, you can override the protection provided with the following config override: ```ruby # Allowing GET requests will expose you to CVE-2015-9284 OmniAuth.config.allowed_request_methods = [:get, :post] ``` ## [v2.6.0](https://github.com/auth0/omniauth-auth0/tree/v2.6.0) (2021-04-01) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.5.0...v2.6.0) **Added** - Org Support [SDK-2395] [\#124](https://github.com/auth0/omniauth-auth0/pull/124) ([davidpatrick](https://github.com/davidpatrick)) - Add login_hint to permitted params [\#123](https://github.com/auth0/omniauth-auth0/pull/123) ([Roriz](https://github.com/Roriz)) ## [v2.5.0](https://github.com/auth0/omniauth-auth0/tree/v2.5.0) (2021-01-21) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.2...v2.5.0) **Added** - Parsing claims from the id_token [\#120](https://github.com/auth0/omniauth-auth0/pull/120) ([davidpatrick](https://github.com/davidpatrick)) **Changed** - Setup build matrix in CI [\#116](https://github.com/auth0/omniauth-auth0/pull/116) ([dmathieu](https://github.com/dmathieu)) **Fixed** - Fixes params passed to authorize [\#119](https://github.com/auth0/omniauth-auth0/pull/119) ([davidpatrick](https://github.com/davidpatrick)) ## [v2.4.2](https://github.com/auth0/omniauth-auth0/tree/v2.4.2) (2021-01-19) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.1...v2.4.2) **Fixed** - Lock Omniauth to 1.9 in gemspec ## [v2.4.1](https://github.com/auth0/omniauth-auth0/tree/v2.4.1) (2020-10-08) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.0...v2.4.1) **Fixed** - Verify the JWT Signature [\#109](https://github.com/auth0/omniauth-auth0/pull/109) ([jimmyjames](https://github.com/jimmyjames)) ## [v2.4.0](https://github.com/auth0/omniauth-auth0/tree/v2.4.0) (2020-09-22) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.3.1...v2.4.0) **Security** - Bump rack from 2.2.2 to 2.2.3 [\#107](https://github.com/auth0/omniauth-auth0/pull/107) ([dependabot](https://github.com/dependabot)) - Update dependencies [\#100](https://github.com/auth0/omniauth-auth0/pull/100) ([Albalmaceda](https://github.com/Albalmaceda)) **Added** - Add support for screen_hint=signup param [\#103](https://github.com/auth0/omniauth-auth0/pull/103) ([bbean86](https://github.com/bbean86)) - Add support for `connection_scope` in params [\#99](https://github.com/auth0/omniauth-auth0/pull/99) ([felixclack](https://github.com/felixclack)) ## [v2.3.1](https://github.com/auth0/omniauth-auth0/tree/v2.3.1) (2020-03-27) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.3.0...v2.3.1) **Fixed bugs:** - Fixes dependency issue [\#97](https://github.com/auth0/omniauth-auth0/pull/97) ([davidpatrick](https://github.com/davidpatrick)) - Fix "NameError: uninitialized constant OmniAuth::Auth0::TokenValidationError" [\#96](https://github.com/auth0/omniauth-auth0/pull/96) ([stefanwork](https://github.com/stefanwork)) ## [v2.3.0](https://github.com/auth0/omniauth-auth0/tree/v2.3.0) (2020-03-06) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.2.0...v2.3.0) **Added** - Improved OIDC Compliance [\#92](https://github.com/auth0/omniauth-auth0/pull/92) ([davidpatrick](https://github.com/davidpatrick)) ## [v2.2.0](https://github.com/auth0/omniauth-auth0/tree/v2.2.0) (2018-04-18) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.1.0...v2.2.0) **Closed issues** - It supports custom domain? [\#71](https://github.com/auth0/omniauth-auth0/issues/71) - Valid Login, No Details: email=nil image=nil name="github|38257089" nickname=nil [\#70](https://github.com/auth0/omniauth-auth0/issues/70) **Added** - Custom issuer [\#77](https://github.com/auth0/omniauth-auth0/pull/77) ([ryan-rosenfeld](https://github.com/ryan-rosenfeld)) - Add telemetry to token endpoint [\#74](https://github.com/auth0/omniauth-auth0/pull/74) ([joshcanhelp](https://github.com/joshcanhelp)) **Changed** - Remove telemetry from authorize URL [\#75](https://github.com/auth0/omniauth-auth0/pull/75) ([joshcanhelp](https://github.com/joshcanhelp)) ## [v2.1.0](https://github.com/auth0/omniauth-auth0/tree/v2.1.0) (2018-10-30) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.0.0...v2.1.0) **Closed issues** - URL should be spelled uppercase outside of code [\#64](https://github.com/auth0/omniauth-auth0/issues/64) - Add prompt=none authorization param handler [\#58](https://github.com/auth0/omniauth-auth0/issues/58) - Could not find a valid mapping for path "/auth/oauth2/callback" [\#56](https://github.com/auth0/omniauth-auth0/issues/56) - I had to downgrade my gems to use this strategy :-( [\#53](https://github.com/auth0/omniauth-auth0/issues/53) - CSRF detected [\#49](https://github.com/auth0/omniauth-auth0/issues/49) - /auth/:provider route not registered? [\#47](https://github.com/auth0/omniauth-auth0/issues/47) **Added** - Add ID token validation [\#62](https://github.com/auth0/omniauth-auth0/pull/62) ([joshcanhelp](https://github.com/joshcanhelp)) - Silent authentication [\#59](https://github.com/auth0/omniauth-auth0/pull/59) ([batalla3692](https://github.com/batalla3692)) - Pass connection parameter to auth0 [\#54](https://github.com/auth0/omniauth-auth0/pull/54) ([tomgi](https://github.com/tomgi)) **Changed** - Update to omniauth-oauth2 [\#55](https://github.com/auth0/omniauth-auth0/pull/55) ([chills42](https://github.com/chills42)) **Fixed** - Fix Rubocop errors [\#66](https://github.com/auth0/omniauth-auth0/pull/66) ([joshcanhelp](https://github.com/joshcanhelp)) - Fix minute bug in README.md [\#63](https://github.com/auth0/omniauth-auth0/pull/63) ([rahuldess](https://github.com/rahuldess)) ## [v2.0.0](https://github.com/auth0/omniauth-auth0/tree/v2.0.0) (2017-01-25) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v2.0.0) Updated library to handle OIDC conformant clients and OAuth2 features in Auth0. 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. 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). The `info` object will use the [OmniAuth schema](https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later) after calling /userinfo: - name: `name` attribute in userinfo response or `sub` if not available. - email: `email` attribute in userinfo response. - nickname: `nickname` attribute in userinfo response. - image: `picture` attribute in userinfo response. Also in `extra` will have in `raw_info` the full /userinfo response. **Fixed** - Use image attribute of omniauth instead of picture [\#45](https://github.com/auth0/omniauth-auth0/pull/45) ([hzalaz](https://github.com/hzalaz)) - Rework strategy to handle OAuth and OIDC [\#44](https://github.com/auth0/omniauth-auth0/pull/44) ([hzalaz](https://github.com/hzalaz)) - lock v10 update, dependencies update [\#41](https://github.com/auth0/omniauth-auth0/pull/41) ([Amialc](https://github.com/Amialc)) ## [v1.4.2](https://github.com/auth0/omniauth-auth0/tree/v1.4.2) (2016-06-13) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.1...v1.4.2) **Added** - Link to OmniAuth site [\#36](https://github.com/auth0/omniauth-auth0/pull/36) ([jghaines](https://github.com/jghaines)) - add ssl fix to RoR example [\#31](https://github.com/auth0/omniauth-auth0/pull/31) ([Amialc](https://github.com/Amialc)) - Update LICENSE [\#17](https://github.com/auth0/omniauth-auth0/pull/17) ([aguerere](https://github.com/aguerere)) **Changed** - Update lock to version 9 [\#34](https://github.com/auth0/omniauth-auth0/pull/34) ([Annyv2](https://github.com/Annyv2)) - Update Gemfile [\#22](https://github.com/auth0/omniauth-auth0/pull/22) ([Annyv2](https://github.com/Annyv2)) - Update lock [\#15](https://github.com/auth0/omniauth-auth0/pull/15) ([Annyv2](https://github.com/Annyv2)) **Fixed** - Fix setup [\#38](https://github.com/auth0/omniauth-auth0/pull/38) ([deepak](https://github.com/deepak)) - Added missing instruction [\#30](https://github.com/auth0/omniauth-auth0/pull/30) ([Annyv2](https://github.com/Annyv2)) - Fixes undefined Auth0Lock issue [\#28](https://github.com/auth0/omniauth-auth0/pull/28) ([Annyv2](https://github.com/Annyv2)) - Update Readme [\#27](https://github.com/auth0/omniauth-auth0/pull/27) ([Annyv2](https://github.com/Annyv2)) ## [v1.4.1](https://github.com/auth0/omniauth-auth0/tree/v1.4.1) (2015-11-18) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v1.4.0...v1.4.1) **Merged pull requests:** - Updating the strategy to set the refresh token in the credentials [\#14](https://github.com/auth0/omniauth-auth0/pull/14) ([LindseyB](https://github.com/LindseyB)) - Update README.md [\#13](https://github.com/auth0/omniauth-auth0/pull/13) ([Annyv2](https://github.com/Annyv2)) - Update home.js [\#12](https://github.com/auth0/omniauth-auth0/pull/12) ([Annyv2](https://github.com/Annyv2)) - Add nested module in version.rb [\#9](https://github.com/auth0/omniauth-auth0/pull/9) ([l4u](https://github.com/l4u)) ## [v1.4.0](https://github.com/auth0/omniauth-auth0/tree/v1.4.0) (2015-06-01) **Merged pull requests:** - Client headers [\#8](https://github.com/auth0/omniauth-auth0/pull/8) ([benschwarz](https://github.com/benschwarz)) - Web application seed with Lock [\#5](https://github.com/auth0/omniauth-auth0/pull/5) ([sandrinodimattia](https://github.com/sandrinodimattia)) - Create LICENSE.md [\#4](https://github.com/auth0/omniauth-auth0/pull/4) ([pose](https://github.com/pose)) - Update README.md [\#3](https://github.com/auth0/omniauth-auth0/pull/3) ([pose](https://github.com/pose)) - Fix Markdown typo [\#2](https://github.com/auth0/omniauth-auth0/pull/2) ([dentarg](https://github.com/dentarg)) \* _This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)_ omniauth-auth0-3.1.0/.rubocop.yml 0000644 0000041 0000041 00000000560 14350575705 016674 0 ustar www-data www-data Metrics/BlockLength: Exclude: - 'Rakefile' - '**/*.rake' - 'spec/**/*.rb' - 'spec/spec_helper.rb' Metrics/MethodLength: Exclude: - 'Rakefile' - '**/*.rake' - 'spec/**/*.rb' Metrics/AbcSize: Exclude: - 'Rakefile' - '**/*.rake' - 'spec/**/*.rb' - 'spec/spec_helper.rb' AllCops: Exclude: - 'omniauth-auth0.gemspec' omniauth-auth0-3.1.0/.gitignore 0000644 0000041 0000041 00000000150 14350575705 016405 0 ustar www-data www-data .ruby-version coverage *.gem .#* .env log/ tmp/ ## Environment normalization: /.bundle /vendor/bundle omniauth-auth0-3.1.0/.snyk 0000644 0000041 0000041 00000000471 14350575705 015410 0 ustar www-data www-data # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. version: v1.13.5 # ignores vulnerabilities until expiry date; change duration by modifying expiry date ignore: SNYK-RUBY-OMNIAUTH-174820: - '*': reason: Not affected. expires: 2020-01-01T00:00:00.000Z patch: {} omniauth-auth0-3.1.0/LICENSE 0000644 0000041 0000041 00000002135 14350575705 015427 0 ustar www-data www-data The MIT License (MIT) Copyright (c) 2015 Auth0, Inc.