diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..612c9ed
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,49 @@
+version: 2.1
+jobs:
+  run-tests:
+    docker:
+      - image: circleci/ruby:2.4.6-jessie
+    steps:
+      - checkout
+      - restore_cache:
+          keys:
+            - gems-v2-{{ checksum "Gemfile.lock" }}
+            - gems-v2-
+      - run: bundle check || bundle install
+      - persist_to_workspace:
+          root: .
+          paths:
+            - Gemfile
+            - Gemfile.lock
+      - save_cache:
+          key: gems-v2--{{ checksum "Gemfile.lock" }}
+          paths:
+            - vendor/bundle
+      - run: bundle exec rake spec
+  snyk:
+    docker:
+      - image: snyk/snyk-cli:rubygems
+    steps:
+      - attach_workspace:
+          at: .
+      - run: snyk test
+      - run:
+          command: |
+            if [[ "${CIRCLE_BRANCH}" == "master" ]]
+            then
+            snyk monitor --org=auth0-sdks
+            fi
+          when: always
+
+workflows:
+  tests:
+    jobs:
+      - run-tests
+  snyk:
+    jobs:
+      - run-tests
+      - snyk:
+          # Must define SNYK_TOKEN env
+          context: snyk-env 
+          requires: 
+            - run-tests
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..279488e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,39 @@
+In order to efficiently and accurately address your issue or feature request, please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. Please delete any sections or questions below that do not pertain to this request.
+
+For general support or usage questions, please use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com.).
+
+### Description
+
+Description of the bug or feature request and why it's a problem. Consider including:
+
+- The use case or overall problem you're trying to solve
+- Information about when the problem started
+
+### Prerequisites
+
+* [ ] I have read the [Auth0 contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
+* [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
+* [ ] Did you check the [documentation](https://auth0.com/docs/quickstart/webapp/rails)?
+* [ ] Did you check [Auth0 Community](https://community.auth0.com/tags/rails)?
+* [ ] Are you reporting this to the correct repository? This strategy relies on [OmniAuth](https://github.com/omniauth/omniauth) and the [OmniAuth OAuth2](https://github.com/omniauth/omniauth-oauth2) strategy. 
+* [ ] Are there any related or duplicate [Issues](https://github.com/auth0/omniauth-auth0/issues) or [PRs](https://github.com/auth0/omniauth-auth0/pulls) for this issue?
+
+### Environment
+
+Please provide the following:
+
+* OmniAuth-Auth0 version:
+* Ruby version:
+* Rails veresion:
+* Browser version, if applicable:
+* Additional gems that might be affecting your instance:
+
+### Reproduction
+
+Detail the steps taken to reproduce this error and note if this issue can be reproduced consistently or if it is intermittent.
+
+Please include:
+
+- Log files (redact/remove sensitive information)
+- Application settings (redact/remove sensitive information)
+- Screenshots, if helpful
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..501b63b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,32 @@
+### Changes
+
+Please describe both what is changing and why this is important. Include:
+
+- Endpoints added, deleted, deprecated, or changed
+- Classes and methods added, deleted, deprecated, or changed
+- Screenshots of new or changed UI, if applicable
+- 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)
+
+### References
+
+Please include relevant links supporting this change such as a:
+
+- support ticket
+- community post
+- StackOverflow post
+- support forum thread
+- related GitHub issue in this or another repo
+
+### Testing
+
+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.
+
+* [ ] This change adds unit test coverage
+* [ ] This change has been tested on the latest version of the platform/language or why not
+
+### Checklist
+
+* [ ] I have read the [Auth0 contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
+* [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
+* [ ] All existing and new tests complete without errors
+* [ ] All code quality tools/guidelines in the [CONTRIBUTING documentation](CONTRIBUTING.md) have been run/followed
diff --git a/.gitignore b/.gitignore
index 8ff167d..2c4eff0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,12 @@
 .ruby-version
 coverage
-Gemfile.lock
 *.gem
 
 .#*
 .env
 log/
-tmp/
\ No newline at end of file
+tmp/
+
+## Environment normalization:
+/.bundle
+/vendor/bundle
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a6c5fe7..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-language: ruby
-rvm:
-- 2.2.5
-branches:
-  only:
-    - master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d54c3a0..efbcbe3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,47 @@
 # Change Log
 
+## [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 differnt when using OAuth2/OIDC features.
+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).
 
@@ -17,6 +54,31 @@ The `info` object will use the [OmniAuth schema](https://github.com/omniauth/omn
 
 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)
 
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..6d49e2f
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Code of Conduct
+
+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.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..36ea532
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,71 @@
+# Contribution
+
+**Thank you in advance for your contribution!**
+
+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. 
+
+## Environment setup
+
+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:
+
+```bash
+$ pwd
+/PROJECT_ROOT/
+$ mkdir vendor # if one does not exist
+$ echo "/vendor/" >> .gitignore
+$ git clone git@github.com:auth0/omniauth-auth0.git vendor/omniauth-auth0
+Cloning into 'vendor/omniauth-auth0'...
+```
+
+Now, run the following command in your project root directory:
+
+```bash
+$ bundle config --local local.omniauth-auth0 /PROJECT_ROOT/vendor/omniauth-auth0
+You are replacing the current local value of local.omniauth-auth0, which is currently nil
+$ bundle config
+Settings are listed in order of priority. The top value will be used.
+local.omniauth-auth0
+Set for your local app (/PROJECT_ROOT/.bundle/config): "/PROJECT_ROOT/vendor/omniauth-auth0"
+```
+
+Finally, add or change the gem include to add a `github:` param:
+
+```ruby
+source 'https://rubygems.org'
+# ...
+# OmniAuth strategy for authenticating with Auth0
+gem 'omniauth-auth0', github: 'auth0/omniauth-auth0'
+#..
+```
+
+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.
+
+[Great explanation for why this setup works well](https://rossta.net/blog/how-to-specify-local-ruby-gems-in-your-gemfile.html). 
+
+## Testing
+
+Tests should be added for additional or modified functionality and all tests should run successfully before submitting a PR. 
+
+### Adding tests
+
+All new tests should be added to the `/spec/omniauth` directory. Testing resources, like JSON fixtures, should be added to the `/spec/resources` directory.
+
+### Running tests
+
+Running tests is as simple as:
+
+```bash
+$ bundle exec rake spec
+```
+
+## Documentation
+
+Documentation for this gem is primarily done at the code level. All new methods should include a docblock at least. 
+
+## Code quality tools
+
+Code quality is enforced across the entire gem with Rubocop:
+
+```bash
+$ bundle exec rake rubocop
+```
diff --git a/Gemfile b/Gemfile
index 285a398..7c61c82 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,13 +1,15 @@
-source 'http://rubygems.org'
+source 'https://rubygems.org'
 
 gemspec
 
 gem 'gem-release'
+gem 'jwt'
 gem 'rake'
 
 group :development do
   gem 'dotenv'
   gem 'pry'
+  gem 'rubocop', require: false
   gem 'shotgun'
   gem 'sinatra'
   gem 'thin'
@@ -18,9 +20,7 @@ group :test do
   gem 'listen', '~> 3.1.5'
   gem 'rack-test'
   gem 'rspec', '~> 3.5'
-  gem 'rubocop', '>= 0.30', platforms: [
-    :ruby_19, :ruby_20, :ruby_21, :ruby_22
-  ]
+  gem 'codecov', require: false
   gem 'simplecov'
   gem 'webmock'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..92d486d
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,167 @@
+PATH
+  remote: .
+  specs:
+    omniauth-auth0 (2.2.0)
+      omniauth-oauth2 (~> 1.5)
+
+GEM
+  remote: https://rubygems.org/
+  specs:
+    addressable (2.6.0)
+      public_suffix (>= 2.0.2, < 4.0)
+    ast (2.4.0)
+    codecov (0.1.14)
+      json
+      simplecov
+      url
+    coderay (1.1.2)
+    crack (0.4.3)
+      safe_yaml (~> 1.0.0)
+    daemons (1.3.1)
+    diff-lcs (1.3)
+    docile (1.3.1)
+    dotenv (2.7.2)
+    eventmachine (1.2.7)
+    faraday (0.15.4)
+      multipart-post (>= 1.2, < 3)
+    ffi (1.10.0)
+    formatador (0.2.5)
+    gem-release (2.0.1)
+    guard (2.15.0)
+      formatador (>= 0.2.4)
+      listen (>= 2.7, < 4.0)
+      lumberjack (>= 1.0.12, < 2.0)
+      nenv (~> 0.1)
+      notiffany (~> 0.0)
+      pry (>= 0.9.12)
+      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 (0.3.8)
+    hashie (3.6.0)
+    jaro_winkler (1.5.2)
+    json (2.2.0)
+    jwt (2.1.0)
+    listen (3.1.5)
+      rb-fsevent (~> 0.9, >= 0.9.4)
+      rb-inotify (~> 0.9, >= 0.9.7)
+      ruby_dep (~> 1.2)
+    lumberjack (1.0.13)
+    method_source (0.9.2)
+    multi_json (1.13.1)
+    multi_xml (0.6.0)
+    multipart-post (2.0.0)
+    mustermann (1.0.3)
+    nenv (0.3.0)
+    notiffany (0.1.1)
+      nenv (~> 0.1)
+      shellany (~> 0.0)
+    oauth2 (1.4.1)
+      faraday (>= 0.8, < 0.16.0)
+      jwt (>= 1.0, < 3.0)
+      multi_json (~> 1.3)
+      multi_xml (~> 0.5)
+      rack (>= 1.2, < 3)
+    omniauth (1.9.0)
+      hashie (>= 3.4.6, < 3.7.0)
+      rack (>= 1.6.2, < 3)
+    omniauth-oauth2 (1.6.0)
+      oauth2 (~> 1.1)
+      omniauth (~> 1.9)
+    parallel (1.17.0)
+    parser (2.6.2.1)
+      ast (~> 2.4.0)
+    pry (0.12.2)
+      coderay (~> 1.1.0)
+      method_source (~> 0.9.0)
+    psych (3.1.0)
+    public_suffix (3.0.3)
+    rack (2.0.7)
+    rack-protection (2.0.5)
+      rack
+    rack-test (1.1.0)
+      rack (>= 1.0, < 3)
+    rainbow (3.0.0)
+    rake (12.3.2)
+    rb-fsevent (0.10.3)
+    rb-inotify (0.10.0)
+      ffi (~> 1.0)
+    rspec (3.8.0)
+      rspec-core (~> 3.8.0)
+      rspec-expectations (~> 3.8.0)
+      rspec-mocks (~> 3.8.0)
+    rspec-core (3.8.0)
+      rspec-support (~> 3.8.0)
+    rspec-expectations (3.8.2)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.8.0)
+    rspec-mocks (3.8.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.8.0)
+    rspec-support (3.8.0)
+    rubocop (0.67.2)
+      jaro_winkler (~> 1.5.1)
+      parallel (~> 1.10)
+      parser (>= 2.5, != 2.5.1.1)
+      psych (>= 3.1.0)
+      rainbow (>= 2.2.2, < 4.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 1.6)
+    ruby-progressbar (1.10.0)
+    ruby_dep (1.5.0)
+    safe_yaml (1.0.5)
+    shellany (0.0.1)
+    shotgun (0.9.2)
+      rack (>= 1.0)
+    simplecov (0.16.1)
+      docile (~> 1.1)
+      json (>= 1.8, < 3)
+      simplecov-html (~> 0.10.0)
+    simplecov-html (0.10.2)
+    sinatra (2.0.5)
+      mustermann (~> 1.0)
+      rack (~> 2.0)
+      rack-protection (= 2.0.5)
+      tilt (~> 2.0)
+    thin (1.7.2)
+      daemons (~> 1.0, >= 1.0.9)
+      eventmachine (~> 1.0, >= 1.0.4)
+      rack (>= 1, < 3)
+    thor (0.20.3)
+    tilt (2.0.9)
+    unicode-display_width (1.5.0)
+    url (0.3.2)
+    webmock (3.5.1)
+      addressable (>= 2.3.6)
+      crack (>= 0.3.2)
+      hashdiff
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  bundler (~> 1.9)
+  codecov
+  dotenv
+  gem-release
+  guard-rspec
+  jwt
+  listen (~> 3.1.5)
+  omniauth-auth0!
+  pry
+  rack-test
+  rake
+  rspec (~> 3.5)
+  rubocop
+  shotgun
+  simplecov
+  sinatra
+  thin
+  webmock
+
+BUNDLED WITH
+   1.17.3
diff --git a/README.md b/README.md
index 6363baf..acb4838 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,58 @@
-[![Build Status](https://travis-ci.org/auth0/omniauth-auth0.svg)](https://travis-ci.org/auth0/omniauth-auth0)
-
 # OmniAuth Auth0
 
-This is the official [OmniAuth](https://github.com/intridea/omniauth) strategy for authenticating to [Auth0](https://auth0.com).
+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. 
 
-## Installing
+[![CircleCI](https://img.shields.io/circleci/project/github/auth0/omniauth-auth0/master.svg)](https://circleci.com/gh/auth0/omniauth-auth0)
+[![codecov](https://codecov.io/gh/auth0/omniauth-auth0/branch/master/graph/badge.svg)](https://codecov.io/gh/auth0/omniauth-auth0)
+[![Gem Version](https://badge.fury.io/rb/omniauth-auth0.svg)](https://badge.fury.io/rb/omniauth-auth0)
+[![MIT licensed](https://img.shields.io/dub/l/vibe-d.svg?style=flat)](https://github.com/auth0/omniauth-auth0/blob/master/LICENSE)
 
-Add to your `Gemfile`:
+## Table of Contents
 
-```ruby
-gem 'omniauth-auth0'
-```
+- [Documentation](#documentation)
+- [Installation](#installation)
+- [Getting Started](#getting-started)
+- [Contribution](#contribution)
+- [Support + Feedback](#support--feedback)
+- [Vulnerability Reporting](#vulnerability-reporting)
+- [What is Auth0](#what-is-auth0)
+- [License](#license)
 
-Then `bundle install`.
+## Documentation
+
+- [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails)
+- [Sample projects](https://github.com/auth0-samples/auth0-rubyonrails-sample)
 
-## Usage
+## Installation
 
-### Rails
+Add the following line to your `Gemfile`:
 
 ```ruby
-Rails.application.config.middleware.use OmniAuth::Builder do
-  provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
-end
+gem 'omniauth-auth0'
 ```
 
-Then to redirect to your tenant's hosted login page:
+Then install:
 
-```ruby
-redirect_to '/auth/auth0'
+```bash
+$ bundle install
 ```
 
-### Sinatra
+See our [contributing guide](CONTRIBUTING.md) for information on local installation for development. 
 
-```ruby
-use OmniAuth::Builder do
-  provider :auth0, ENV['AUTH0_CLIENT_ID'], ENV['AUTH0_CLIENT_SECRET'], ENV['AUTH0_DOMAIN']
-end
-```
+## Getting Started
 
-Then to redirect to your tenant's hosted login page:
+To start processing authentication requests, the following steps must be performed:
 
-```ruby
-redirect to('/auth/auth0')
-```
+1. Initialize the strategy
+2. Configure the callback controller
+3. Add the required routes
+4. Trigger an authentication request
 
-> You can customize your hosted login page in your [Auth0 Dashboard](https://manage.auth0.com/#/login_page)
+All of these tasks and more are covered in our [Ruby on Rails Quickstart](https://auth0.com/docs/quickstart/webapp/rails).
 
-### Auth parameters
+### Additional authentication parameters
 
-To send additional parameters during login you can specify them when you register the provider
+To send additional parameters during login, you can specify them when you register the provider:
 
 ```ruby
 provider 
@@ -64,80 +68,89 @@ provider
   }
 ```
 
-that will tell it to send those parameters on every Auth request.
+... which will tell the strategy to send those parameters on every Auth request.
 
-Or you can do it for a specific Auth request by adding them in the query parameter of the redirect url:
+Or you can do it for a specific authentication request by adding them to the query parameters of the redirect URL. Allowed parameters are `connection` and `prompt`:
 
 ```ruby
 redirect_to '/auth/auth0?connection=google-oauth2'
+redirect_to '/auth/auth0?prompt=none'
 ```
 
-### Auth Hash
+### Authentication hash
 
-Auth0 strategy will have the standard OmniAuth hash attributes:
+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: Auth0 tokens, at least will have an access_token but can eventually have refresh_token and/or id_token
-- extra: Additional info obtained from calling /userinfo in the attribute `raw_info`
+- `: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 => 'google-oauth2|this-is-the-google-id',
-	  :info => {
-	    :name => 'John Foo',
-	    :email => 'johnfoo@example.org',
-	    :nickname => 'john',
-	    :image => 'https://example.org/john.jpg'
-	  },
-	  :credentials => {
-	    :token => 'XdDadllcas2134rdfdsI',
-	    :expires_at => 1485373937,
-        :expires => true,
-        :refresh_token => 'aKNajdjfj123nBasd',
-	    :id_token => 'eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBGb28ifQ.lxAiy1rqve8ZHQEQVehUlP1sommPHVJDhgPgFPnDosg',
-	    :token_type => 'bearer',
-	  },
-	  :extra => {
-	    :raw_info => {
-	      :email => 'johnfoo@example.org',
-	      :email_verified => 'true',
-	      :name => 'John Foo',
-	      :picture => 'https://example.org/john.jpg',
-	      :user_id => 'google-oauth2|this-is-the-google-id',
-	      :nickname => 'john',
-	      :created_at: '2014-07-15T17:19:50.387Z'
-	    }
-	  }
-	}
+{
+  :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'
+    }
+  }
+}
 ```
 
-### ActionDispatch::Cookies::CookieOverflow issue
+## Contribution
 
-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.
+We appreciate feedback and contribution to this repo! Before you get started, please see the following:
 
-You can change to use In-Memory store for development as follows:
+- [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
+- [Auth0's Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
+- [This repo's contribution guide](CONTRIBUTING.md)
 
-	# /config/initializers/session_store.rb
-	CrazyApp::Application.config.session_store :cache_store
+## Support + Feedback
 
-	# /config/environments/development.rb
-	config.cache_store = :memory_store
 
-## Documentation
+- Use [Community](https://community.auth0.com/) for usage, questions, specific cases.
+- Use [Issues](https://github.com/auth0/omniauth-auth0/issues) here for code-level support and bug reports.
+- Paid customers can use [Support](https://support.auth0.com/) to submit a trouble ticket for production-affecting issues. 
+
+## Vulnerability Reporting
 
-For more information about [auth0](http://auth0.com) contact our [documentation page](http://docs.auth0.com/).
+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.
 
-## Issue Reporting
+## What is Auth0?
 
-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.
+Auth0 helps you to easily:
 
-## Author
+- 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.)
+- log in users with username/password databases, passwordless, or multi-factor authentication
+- link multiple user accounts together
+- generate signed JSON Web Tokens to authorize your API calls and flow the user identity securely
+- access demographics and analytics detailing how, when, and where users are logging in
+- enrich user profiles from other data sources using customizable JavaScript rules
 
-[Auth0](https://auth0.com)
+[Why Auth0?](https://auth0.com/why-auth0)
 
 ## License
 
-This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.
+The OmniAuth Auth0 strategy is licensed under MIT - [LICENSE](LICENSE)
diff --git a/Rakefile b/Rakefile
old mode 100644
new mode 100755
index bc4c39c..6ee0a76
--- a/Rakefile
+++ b/Rakefile
@@ -10,7 +10,7 @@ begin
   RuboCop::RakeTask.new
 rescue LoadError
   task :rubocop do
-    $stderr.puts 'Rubocop is disabled'
+    warn 'Rubocop is disabled'
   end
 end
 
@@ -23,7 +23,7 @@ namespace :sinatra do
 end
 
 desc 'Run specs'
-task default: [:spec, :rubocop]
+task default: %i[spec rubocop]
 task test: :spec
 task :guard do
   system 'bundle exec guard'
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..436c03a
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,22 @@
+coverage:
+  precision: 2
+  round: down
+  range: "60...100"
+  status:
+    project:
+      default:
+        enabled: true
+        target: auto
+        threshold: 5%
+        if_no_uploads: error
+    patch:
+      default:
+        enabled: true
+        target: 80%
+        threshold: 30%
+        if_no_uploads: error
+    changes:
+      default:
+        enabled: true
+        if_no_uploads: error
+comment: false 
diff --git a/debian/changelog b/debian/changelog
index 01361cf..66ae914 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,12 @@
-ruby-omniauth-auth0 (2.0.0-2) UNRELEASED; urgency=medium
+ruby-omniauth-auth0 (2.2.0-1) UNRELEASED; urgency=medium
 
+  [ Utkarsh Gupta ]
   * Add salsa-ci.yml
 
- -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Tue, 13 Aug 2019 06:14:56 +0530
+  [ Debian Janitor ]
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 12 Oct 2019 06:05:55 +0000
 
 ruby-omniauth-auth0 (2.0.0-1) unstable; urgency=medium
 
diff --git a/lib/omniauth-auth0.rb b/lib/omniauth-auth0.rb
index 8981346..4946eb2 100644
--- a/lib/omniauth-auth0.rb
+++ b/lib/omniauth-auth0.rb
@@ -1,2 +1,2 @@
-require 'omniauth-auth0/version' # rubocop:disable Style/FileName
+require 'omniauth-auth0/version'
 require 'omniauth/strategies/auth0'
diff --git a/lib/omniauth-auth0/version.rb b/lib/omniauth-auth0/version.rb
index b16e941..7172ee3 100644
--- a/lib/omniauth-auth0/version.rb
+++ b/lib/omniauth-auth0/version.rb
@@ -1,5 +1,5 @@
 module OmniAuth
   module Auth0
-    VERSION = '2.0.0'.freeze
+    VERSION = '2.2.0'.freeze
   end
 end
diff --git a/lib/omniauth/auth0/jwt_validator.rb b/lib/omniauth/auth0/jwt_validator.rb
new file mode 100644
index 0000000..6ac0eab
--- /dev/null
+++ b/lib/omniauth/auth0/jwt_validator.rb
@@ -0,0 +1,134 @@
+require 'base64'
+require 'uri'
+require 'json'
+require 'omniauth'
+
+module OmniAuth
+  module Auth0
+    # JWT Validator class
+    class JWTValidator
+      attr_accessor :issuer, :domain
+
+      # Initializer
+      # @param options object
+      #   options.domain - Application domain.
+      #   options.issuer - Application issuer (optional).
+      #   options.client_id - Application Client ID.
+      #   options.client_secret - Application Client Secret.
+      def initialize(options)
+        @domain = uri_string(options.domain)
+
+        # Use custom issuer if provided, otherwise use domain
+        @issuer = @domain
+        @issuer = uri_string(options.issuer) if options.respond_to?(:issuer)
+
+        @client_id = options.client_id
+        @client_secret = options.client_secret
+      end
+
+      # Decode a JWT.
+      # @param jwt string - JWT to decode.
+      # @return hash - The decoded token, if there were no exceptions.
+      # @see https://github.com/jwt/ruby-jwt
+      def decode(jwt)
+        head = token_head(jwt)
+
+        # Make sure the algorithm is supported and get the decode key.
+        decode_key = @client_secret
+        if head[:alg] == 'RS256'
+          decode_key = rs256_decode_key(head[:kid])
+        elsif head[:alg] != 'HS256'
+          raise JWT::VerificationError, :id_token_alg_unsupported
+        end
+
+        # Docs: https://github.com/jwt/ruby-jwt#algorithms-and-usage
+        JWT.decode(jwt, decode_key, true, decode_opts(head[:alg]))
+      end
+
+      # Get the decoded head segment from a JWT.
+      # @return hash - The parsed head of the JWT passed, empty hash if not.
+      def token_head(jwt)
+        jwt_parts = jwt.split('.')
+        return {} if blank?(jwt_parts) || blank?(jwt_parts[0])
+
+        json_parse(Base64.decode64(jwt_parts[0]))
+      end
+
+      # Get the JWKS from the issuer and return a public key.
+      # @param x5c string - X.509 certificate chain from a JWKS.
+      # @return key - The X.509 certificate public key.
+      def jwks_public_cert(x5c)
+        x5c = Base64.decode64(x5c)
+
+        # https://docs.ruby-lang.org/en/2.4.0/OpenSSL/X509/Certificate.html
+        OpenSSL::X509::Certificate.new(x5c).public_key
+      end
+
+      # Return a specific key from a JWKS object.
+      # @param key string - Key to find in the JWKS.
+      # @param kid string - Key ID to identify the right JWK.
+      # @return nil|string
+      def jwks_key(key, kid)
+        return nil if blank?(jwks[:keys])
+
+        matching_jwk = jwks[:keys].find { |jwk| jwk[:kid] == kid }
+        matching_jwk[key] if matching_jwk
+      end
+
+      private
+
+      # Get the JWT decode options
+      # Docs: https://github.com/jwt/ruby-jwt#add-custom-header-fields
+      # @return hash
+      def decode_opts(alg)
+        {
+          algorithm: alg,
+          leeway: 30,
+          verify_expiration: true,
+          verify_iss: true,
+          iss: @issuer,
+          verify_aud: true,
+          aud: @client_id,
+          verify_not_before: true
+        }
+      end
+
+      def rs256_decode_key(kid)
+        jwks_x5c = jwks_key(:x5c, kid)
+        raise JWT::VerificationError, :jwks_missing_x5c if jwks_x5c.nil?
+
+        jwks_public_cert(jwks_x5c.first)
+      end
+
+      # Get a JWKS from the domain
+      # @return void
+      def jwks
+        jwks_uri = URI(@domain + '.well-known/jwks.json')
+        @jwks ||= json_parse(Net::HTTP.get(jwks_uri))
+      end
+
+      # Rails Active Support blank method.
+      # @param obj object - Object to check for blankness.
+      # @return boolean
+      def blank?(obj)
+        obj.respond_to?(:empty?) ? obj.empty? : !obj
+      end
+
+      # Parse JSON with symbolized names.
+      # @param json string - JSON to parse.
+      # @return hash
+      def json_parse(json)
+        JSON.parse(json, symbolize_names: true)
+      end
+
+      # Parse a URI into the desired string format
+      # @param uri - the URI to parse
+      # @return string
+      def uri_string(uri)
+        temp_domain = URI(uri)
+        temp_domain = URI("https://#{uri}") unless temp_domain.scheme
+        "#{temp_domain}/"
+      end
+    end
+  end
+end
diff --git a/lib/omniauth/auth0/telemetry.rb b/lib/omniauth/auth0/telemetry.rb
new file mode 100644
index 0000000..f6ab164
--- /dev/null
+++ b/lib/omniauth/auth0/telemetry.rb
@@ -0,0 +1,36 @@
+require 'json'
+
+module OmniAuth
+  module Auth0
+    # Module to provide necessary telemetry for API requests.
+    module Telemetry
+
+      # Return a telemetry hash to be encoded and sent to Auth0.
+      # @return hash
+      def telemetry
+        telemetry = {
+          name: 'omniauth-auth0',
+          version: OmniAuth::Auth0::VERSION,
+          env: {
+            ruby: RUBY_VERSION
+          }
+        }
+        add_rails_version telemetry
+      end
+
+      # JSON-ify and base64 encode the current telemetry.
+      # @return string
+      def telemetry_encoded
+        Base64.urlsafe_encode64(JSON.dump(telemetry))
+      end
+
+      private
+
+      def add_rails_version(telemetry)
+        return telemetry unless Gem.loaded_specs['rails'].respond_to? :version
+        telemetry[:env][:rails] = Gem.loaded_specs['rails'].version.to_s
+        telemetry
+      end
+    end
+  end
+end
diff --git a/lib/omniauth/strategies/auth0.rb b/lib/omniauth/strategies/auth0.rb
index a89d1c4..d6eb14b 100644
--- a/lib/omniauth/strategies/auth0.rb
+++ b/lib/omniauth/strategies/auth0.rb
@@ -1,19 +1,26 @@
+# frozen_string_literal: true
+
 require 'base64'
 require 'uri'
 require 'omniauth-oauth2'
+require 'omniauth/auth0/jwt_validator'
+require 'omniauth/auth0/telemetry'
 
 module OmniAuth
   module Strategies
     # Auth0 OmniAuth strategy
     class Auth0 < OmniAuth::Strategies::OAuth2
+      include OmniAuth::Auth0::Telemetry
+
       option :name, 'auth0'
 
-      args [
-        :client_id,
-        :client_secret,
-        :domain
+      args %i[
+        client_id
+        client_secret
+        domain
       ]
 
+      # Setup client URLs used during authentication
       def client
         options.client_options.site = domain_url
         options.client_options.authorize_url = '/authorize'
@@ -22,25 +29,42 @@ module OmniAuth
         super
       end
 
+      # Use the "sub" key of the userinfo returned
+      # as the uid (globally unique string identifier).
       uid { raw_info['sub'] }
 
+      # Build the API credentials hash with returned auth data.
       credentials do
-        hash = { 'token' => access_token.token }
-        hash['expires'] = true
+        credentials = {
+          'token' => access_token.token,
+          'expires' => true
+        }
+
         if access_token.params
-          hash['id_token'] = access_token.params['id_token']
-          hash['token_type'] = access_token.params['token_type']
-          hash['refresh_token'] = access_token.refresh_token
+          credentials.merge!(
+            'id_token' => access_token.params['id_token'],
+            'token_type' => access_token.params['token_type'],
+            'refresh_token' => access_token.refresh_token
+          )
         end
-        hash
+
+        # Make sure the ID token can be verified and decoded.
+        auth0_jwt = OmniAuth::Auth0::JWTValidator.new(options)
+        jwt_decoded = auth0_jwt.decode(credentials['id_token'])
+        fail!(:invalid_id_token) unless jwt_decoded.length
+
+        credentials
       end
 
+      # Store all raw information for use in the session.
       extra do
         {
           raw_info: raw_info
         }
       end
 
+      # Build a hash of information about the user
+      # with keys taken from the Auth Hash Schema.
       info do
         {
           name: raw_info['name'] || raw_info['sub'],
@@ -50,56 +74,66 @@ module OmniAuth
         }
       end
 
+      # Define the parameters used for the /authorize endpoint
       def authorize_params
         params = super
-        params['auth0Client'] = client_info
+        parsed_query = Rack::Utils.parse_query(request.query_string)
+        params['connection'] = parsed_query['connection']
+        params['prompt'] = parsed_query['prompt']
         params
       end
 
+      def build_access_token
+        options.token_params[:headers] = { 'Auth0-Client' => telemetry_encoded }
+        super
+      end
+
+      # Declarative override for the request phase of authentication
       def request_phase
         if no_client_id?
+          # Do we have a client_id for this Application?
           fail!(:missing_client_id)
         elsif no_client_secret?
+          # Do we have a client_secret for this Application?
           fail!(:missing_client_secret)
         elsif no_domain?
+          # Do we have a domain for this Application?
           fail!(:missing_domain)
         else
+          # All checks pass, run the Oauth2 request_phase method.
           super
         end
       end
 
       private
 
+      # Parse the raw user info.
       def raw_info
         userinfo_url = options.client_options.userinfo_url
         @raw_info ||= access_token.get(userinfo_url).parsed
       end
 
+      # Check if the options include a client_id
       def no_client_id?
         ['', nil].include?(options.client_id)
       end
 
+      # Check if the options include a client_secret
       def no_client_secret?
         ['', nil].include?(options.client_secret)
       end
 
+      # Check if the options include a domain
       def no_domain?
         ['', nil].include?(options.domain)
       end
 
+      # Normalize a domain to a URL.
       def domain_url
         domain_url = URI(options.domain)
         domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
         domain_url.to_s
       end
-
-      def client_info
-        client_info = JSON.dump(
-          name: 'omniauth-auth0',
-          version: OmniAuth::Auth0::VERSION
-        )
-        Base64.urlsafe_encode64(client_info)
-      end
     end
   end
 end
diff --git a/omniauth-auth0.gemspec b/omniauth-auth0.gemspec
index 43b9fce..b28b8de 100644
--- a/omniauth-auth0.gemspec
+++ b/omniauth-auth0.gemspec
@@ -8,12 +8,12 @@ Gem::Specification.new do |s|
   s.authors     = ['Auth0']
   s.email       = ['info@auth0.com']
   s.homepage    = 'https://github.com/auth0/omniauth-auth0'
-  s.summary     = 'Omniauth OAuth2 strategy for the Auth0 platform.'
+  s.summary     = 'OmniAuth OAuth2 strategy for the Auth0 platform.'
   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.
 
 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.
 
-omniauth-auth0 is the omniauth strategy for Auth0.
+omniauth-auth0 is the OmniAuth strategy for Auth0.
 }
 
   s.rubyforge_project = 'omniauth-auth0'
@@ -23,7 +23,7 @@ omniauth-auth0 is the omniauth strategy for Auth0.
   s.executables   = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
   s.require_paths = ['lib']
 
-  s.add_runtime_dependency 'omniauth-oauth2', '~> 1.4'
+  s.add_runtime_dependency 'omniauth-oauth2', '~> 1.5'
 
   s.add_development_dependency 'bundler', '~> 1.9'
   
diff --git a/spec/omniauth/auth0/jwt_validator_spec.rb b/spec/omniauth/auth0/jwt_validator_spec.rb
new file mode 100644
index 0000000..4c2db37
--- /dev/null
+++ b/spec/omniauth/auth0/jwt_validator_spec.rb
@@ -0,0 +1,333 @@
+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(:jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
+
+  let(:rsa_private_key) do
+    OpenSSL::PKey::RSA.generate 2048
+  end
+
+  let(:rsa_token_jwks) do
+    {
+      keys: [
+        {
+          kid: 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' do
+    let(:jwt_validator) do
+      make_jwt_validator
+    end
+
+    before do
+      stub_jwks
+    end
+
+    it 'should return a key' do
+      expect(jwt_validator.jwks_key(:alg, jwks_kid)).to eq('RS256')
+    end
+
+    it 'should return an x5c key' do
+      expect(jwt_validator.jwks_key(:x5c, jwks_kid).length).to eq(1)
+    end
+
+    it 'should return nil if there is not key' do
+      expect(jwt_validator.jwks_key(:auth0, jwks_kid)).to eq(nil)
+    end
+
+    it 'should return nil if the key ID is invalid' do
+      expect(jwt_validator.jwks_key(:alg, "#{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
+      let(:jwt_validator) do
+        make_jwt_validator(opt_issuer: 'different.auth0.com')
+      end
+
+      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
+  end
+
+  describe 'JWT verifier decode' do
+    let(:jwt_validator) do
+      make_jwt_validator
+    end
+
+    before do
+      stub_jwks
+      stub_dummy_jwks
+    end
+
+    it 'should fail with passed expiration' do
+      payload = {
+        exp: past_timecode
+      }
+      token = make_hs256_token(payload)
+      expect do
+        jwt_validator.decode(token)
+      end.to raise_error(JWT::ExpiredSignature)
+    end
+
+    it 'should fail with missing issuer' do
+      expect do
+        jwt_validator.decode(make_hs256_token)
+      end.to raise_error(JWT::InvalidIssuerError)
+    end
+
+    it 'should fail with invalid issuer' do
+      payload = {
+        iss: 'https://auth0.com/'
+      }
+      token = make_hs256_token(payload)
+      expect do
+        jwt_validator.decode(token)
+      end.to raise_error(JWT::InvalidIssuerError)
+    end
+
+    it 'should fail with a future not before' do
+      payload = {
+        nbf: future_timecode,
+        iss: "https://#{domain}/"
+      }
+      token = make_hs256_token(payload)
+      expect do
+        jwt_validator.decode(token)
+      end.to raise_error(JWT::ImmatureSignature)
+    end
+
+    it 'should fail with missing audience' do
+      payload = {
+        iss: "https://#{domain}/"
+      }
+      token = make_hs256_token(payload)
+      expect do
+        jwt_validator.decode(token)
+      end.to raise_error(JWT::InvalidAudError)
+    end
+
+    it 'should fail with invalid audience' do
+      payload = {
+        iss: "https://#{domain}/",
+        aud: 'Auth0'
+      }
+      token = make_hs256_token(payload)
+      expect do
+        jwt_validator.decode(token)
+      end.to raise_error(JWT::InvalidAudError)
+    end
+
+    it 'should decode a valid HS256 token with multiple audiences' do
+      payload = {
+        iss: "https://#{domain}/",
+        aud: [
+          client_id,
+          "https://#{domain}/userinfo"
+        ]
+      }
+      token = make_hs256_token(payload)
+      expect(jwt_validator.decode(token).length).to eq(2)
+    end
+
+    it 'should decode a standard HS256 token' do
+      sub = 'abc123'
+      payload = {
+        sub: sub,
+        exp: future_timecode,
+        iss: "https://#{domain}/",
+        iat: past_timecode,
+        aud: client_id
+      }
+      token = make_hs256_token(payload)
+      decoded_token = jwt_validator.decode(token)
+      expect(decoded_token.first['sub']).to eq(sub)
+    end
+
+    it 'should decode 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,
+        kid: jwks_kid
+      }
+      token = make_rs256_token(payload)
+      decoded_token = make_jwt_validator(opt_domain: domain).decode(token)
+      expect(decoded_token.first['sub']).to eq(sub)
+    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)
+    payload = { sub: 'abc123' } if payload.nil?
+    JWT.encode payload, client_secret, 'HS256'
+  end
+
+  def make_rs256_token(payload = nil)
+    payload = { sub: 'abc123' } if payload.nil?
+    JWT.encode payload, rsa_private_key, 'RS256', kid: jwks_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_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_bad_jwks
+    stub_request(:get, 'https://samples.auth0.com/.well-known/jwks-bad.json')
+      .to_return(
+        status: 404
+      )
+  end
+
+  def stub_dummy_jwks
+    stub_request(:get, 'https://example.org/.well-known/jwks.json')
+      .to_return(
+        headers: { 'Content-Type' => 'application/json' },
+        body: rsa_token_jwks,
+        status: 200
+      )
+  end
+end
diff --git a/spec/omniauth/auth0/telemetry_spec.rb b/spec/omniauth/auth0/telemetry_spec.rb
new file mode 100644
index 0000000..d6f351c
--- /dev/null
+++ b/spec/omniauth/auth0/telemetry_spec.rb
@@ -0,0 +1,28 @@
+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
diff --git a/spec/omniauth/strategies/auth0_spec.rb b/spec/omniauth/strategies/auth0_spec.rb
index 11a7bd6..c79baf6 100644
--- a/spec/omniauth/strategies/auth0_spec.rb
+++ b/spec/omniauth/strategies/auth0_spec.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
 require 'spec_helper'
+require 'jwt'
 
 RSpec.shared_examples 'site has valid domain url' do |url|
   it { expect(subject.site).to eq(url) }
@@ -78,6 +81,20 @@ describe OmniAuth::Strategies::Auth0 do
       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')
+    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')
     end
 
     describe 'callback' do
@@ -85,7 +102,7 @@ describe OmniAuth::Strategies::Auth0 do
       let(:expires_in) { 2000 }
       let(:token_type) { 'bearer' }
       let(:refresh_token) { 'refresh token' }
-      let(:id_token) { 'id token' }
+      let(:telemetry_value) { Class.new.extend(OmniAuth::Auth0::Telemetry).telemetry_encoded }
 
       let(:user_id) { 'user identifier' }
       let(:state) { SecureRandom.hex(8) }
@@ -95,8 +112,17 @@ describe OmniAuth::Strategies::Auth0 do
       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
+        JWT.encode payload, client_secret, 'HS256'
+      end
+
       let(:oauth_response) do
         {
+          id_token: id_token,
           access_token: access_token,
           expires_in: expires_in,
           token_type: token_type
@@ -126,6 +152,7 @@ describe OmniAuth::Strategies::Auth0 do
 
       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)
diff --git a/spec/resources/jwks.json b/spec/resources/jwks.json
new file mode 100644
index 0000000..97f8d97
--- /dev/null
+++ b/spec/resources/jwks.json
@@ -0,0 +1,28 @@
+{
+  "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"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d5c6488..a5b5433 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,12 +1,14 @@
-$LOAD_PATH.unshift File.expand_path('..', __FILE__)
-$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
+$LOAD_PATH.unshift File.expand_path(__dir__)
+$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
 
 require 'simplecov'
-if ENV['COVERAGE']
-  SimpleCov.start do
-    minimum_coverage(89.8)
-  end
+SimpleCov.start
+
+if ENV['CI'] == 'true'
+  require 'codecov'
+  SimpleCov.formatter = SimpleCov::Formatter::Codecov
 end
+
 require 'rspec'
 require 'rack/test'
 require 'webmock/rspec'