auth0_rs256_jwt_verifier 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 62a95cfb84c90e4b8059e318f2c7bfcee71e8b27
4
+ data.tar.gz: 9e6cde7f7da12eeb4ddde673900eb9eb18b2a5d3
5
+ SHA512:
6
+ metadata.gz: e5d78f48de5792166fff6903c7f3740be6bf1f5bb279971deba1363ce5e5151866d22b09f74508fb44c50248193372ab345410b2af921a7f9ba493b3a8eefd4e
7
+ data.tar.gz: 93a0adcddc9c89f60e846fe4331a1308d5c4c04d35098206d5cf105abea32ab06a2b36622cdda900e6d95cec243430bdfe8560f73e982153f682dcf591c8c397
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ Gemfile.lock
@@ -0,0 +1,9 @@
1
+ inherit_gem:
2
+ sanelint:
3
+ - config/default.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.4
7
+
8
+ Style/Documentation:
9
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+ ruby "~> 2.4.0"
4
+
5
+ gem "minitest", "~> 5"
6
+ gem "rake", "~> 12.0.0"
7
+ gem "http", "~>2.2.2"
8
+ gem "json-jwt", "~> 1.7.2"
@@ -0,0 +1,6 @@
1
+ # Auth0 JWT (RS256) verification library
2
+ [Auth0](https://auth0.com) is web service handling users identities which can be easily plugged
3
+ into your application. It provides [SDKs](https://auth0.com/docs) for many languages which enable you to sign up/in users
4
+ and returns access token ([JWT](https://jwt.io)) in exchange. Access token can be used then to access your's Web Service.
5
+ This gem helps you to [verify](https://auth0.com/docs/api-auth/tutorials/verify-access-token#verify-the-signature)
6
+ such access token which has been signed using the RS256 algorithm.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.pattern = "test/**/*_test.rb"
7
+ end
8
+
9
+ desc "Run tests"
10
+ task default: :test
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ Gem::Specification.new do |s|
3
+ s.name = "auth0_rs256_jwt_verifier"
4
+ s.version = "0.0.1"
5
+ s.date = "2017-06-12"
6
+ s.summary = "Auth0 JWT (RS256) verification library"
7
+ s.description = <<-DESCRIPTION.gsub(/\s+/, " ").strip
8
+ Auth0 (https://auth0.com) is web service handling users identities which can be easily plugged
9
+ into your application. It provides SDKs for many languages which enable you to sign up/in users
10
+ and returns access token (JWT) in exchange. Access token can be used then to access your's Web Service.
11
+ This gem helps you to verify
12
+ (https://auth0.com/docs/api-auth/tutorials/verify-access-token#verify-the-signature)
13
+ such access token which has been signed using the RS256 algorithm.
14
+ DESCRIPTION
15
+ s.authors = ["Krzysztof Zielonka"]
16
+ s.email = "krzysztof.zielonka@droidsonroids.pl"
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
19
+ s.homepage = "https://rubygems.org/gems/auth0_rs256_jwt_verifier"
20
+ s.license = "MIT"
21
+ s.add_runtime_dependency "http", "~> 2"
22
+ s.add_runtime_dependency "json-jwt", "~> 1.7"
23
+ s.add_development_dependency "rake", "~> 12"
24
+ s.add_development_dependency "minitest", "~> 5"
25
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require "http"
3
+ require "json"
4
+
5
+ require "auth0_jwt_rs256_verifier/user_id"
6
+ require "auth0_jwt_rs256_verifier/results"
7
+ require "auth0_jwt_rs256_verifier/jwt_decoder"
8
+ require "auth0_jwt_rs256_verifier/jwt_decoder_wrapper"
9
+ require "auth0_jwt_rs256_verifier/jwk_set_downloader"
10
+ require "auth0_jwt_rs256_verifier/valid_jwk_set"
11
+ require "auth0_jwt_rs256_verifier/certs_set"
12
+ require "auth0_jwt_rs256_verifier/exp_verifier"
13
+ require "auth0_jwt_rs256_verifier/jwk"
14
+
15
+ class Auth0RS256JWTVerifier
16
+ def initialize(issuer:, audience:, jwks_url:, http: HTTP, exp_verifier: ExpVerifier.new)
17
+ @audience = audience
18
+ @issuer = issuer
19
+ @jwks_url = jwks_url
20
+ @jwks_downloader = JWKSetDownloader.new(http)
21
+ @exp_verifier = exp_verifier
22
+ @certificates = nil
23
+ end
24
+
25
+ def verify(access_token)
26
+ payload = JWTDecoderWrapper.new(
27
+ @audience,
28
+ @issuer,
29
+ certificates,
30
+ exp_verifier: @exp_verifier,
31
+ jwt_decoder: JWTDecoder.new,
32
+ ).decode(access_token)
33
+ Results::ValidAccessToken.new(UserId.new(payload.sub))
34
+ rescue JWTDecoderWrapper::Error
35
+ Results::INVALID_ACCESS_TOKEN
36
+ end
37
+
38
+ private
39
+
40
+ def certificates
41
+ return @certificates if @certificates
42
+ @certificates = CertsSet.new(ValidJWKSet.new(@jwks_downloader.download(@jwks_url)))
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require "http"
3
+ require "json"
4
+
5
+ require "auth0_rs256_jwt_verifier/user_id"
6
+ require "auth0_rs256_jwt_verifier/results"
7
+ require "auth0_rs256_jwt_verifier/jwt_decoder"
8
+ require "auth0_rs256_jwt_verifier/jwt_decoder_wrapper"
9
+ require "auth0_rs256_jwt_verifier/jwk_set_downloader"
10
+ require "auth0_rs256_jwt_verifier/valid_jwk_set"
11
+ require "auth0_rs256_jwt_verifier/certs_set"
12
+ require "auth0_rs256_jwt_verifier/exp_verifier"
13
+ require "auth0_rs256_jwt_verifier/jwk"
14
+
15
+ class Auth0RS256JWTVerifier
16
+ def initialize(issuer:, audience:, jwks_url:, http: HTTP, exp_verifier: ExpVerifier.new)
17
+ @audience = audience
18
+ @issuer = issuer
19
+ @jwks_url = jwks_url
20
+ @jwks_downloader = JWKSetDownloader.new(http)
21
+ @exp_verifier = exp_verifier
22
+ @certificates = nil
23
+ end
24
+
25
+ def verify(access_token)
26
+ payload = JWTDecoderWrapper.new(
27
+ @audience,
28
+ @issuer,
29
+ certificates,
30
+ exp_verifier: @exp_verifier,
31
+ jwt_decoder: JWTDecoder.new,
32
+ ).decode(access_token)
33
+ Results::ValidAccessToken.new(UserId.new(payload.sub))
34
+ rescue JWTDecoderWrapper::Error
35
+ Results::INVALID_ACCESS_TOKEN
36
+ end
37
+
38
+ private
39
+
40
+ def certificates
41
+ return @certificates if @certificates
42
+ @certificates = CertsSet.new(ValidJWKSet.new(@jwks_downloader.download(@jwks_url)))
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require "base64"
3
+ class Auth0RS256JWTVerifier
4
+ class CertsSet
5
+ NotFoundError = Class.new(RuntimeError)
6
+
7
+ def initialize(jwk_set)
8
+ @jwk_set = jwk_set
9
+ end
10
+
11
+ def find(id)
12
+ cert = certs.find { |c| c.id == id }
13
+ raise NotFoundError, "cert #{id} doesn't exist" if cert.nil?
14
+ cert.cert
15
+ end
16
+
17
+ private
18
+
19
+ CertWithId = Struct.new(:id, :cert)
20
+ private_constant :CertWithId
21
+
22
+ def certs
23
+ @certs ||= @jwk_set.map { |jwk| CertWithId.new(jwk.kid, build_cert(jwk)) }
24
+ end
25
+
26
+ def build_cert(jwk)
27
+ encoded = Base64.decode64(String(jwk.x5c.first))
28
+ OpenSSL::X509::Certificate.new(encoded)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class ExpVerifier
4
+ def expired?(exp)
5
+ Time.at(exp).utc < Time.now.utc
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class JWK
4
+ ParseError = Class.new(RuntimeError)
5
+
6
+ def inspect
7
+ "JWK(\n" \
8
+ "\talg: #{@alg},\n" \
9
+ "\tkty: #{@kty},\n" \
10
+ "\tuse: #{@use},\n" \
11
+ "\tx5c: #{@x5c.inspect.split("\n").map { |l| "\t#{l}" }.join("\n")},\n" \
12
+ "\tn: #{@n},\n" \
13
+ "\te: #{@e},\n" \
14
+ "\tkid: #{@kid},\n" \
15
+ "\tx5t: #{@x5t}\n" \
16
+ ")"
17
+ end
18
+
19
+ class JWKMember
20
+ def present?
21
+ raise NotImplementedMethod
22
+ end
23
+ end
24
+
25
+ class OptionalStringJWKMember
26
+ include Comparable
27
+
28
+ def initialize(value)
29
+ if value.nil?
30
+ @value = nil
31
+ elsif value.is_a?(String)
32
+ @value = value
33
+ else
34
+ raise ParseError, "require field #{self.class.name} to be String but is '#{value}'"
35
+ end
36
+ end
37
+
38
+ def <=>(other)
39
+ @value <=> String(other)
40
+ end
41
+
42
+ def to_s
43
+ @value
44
+ end
45
+
46
+ def present?
47
+ !@value.nil?
48
+ end
49
+ end
50
+ private_constant :OptionalStringJWKMember
51
+
52
+ class RequiredStringJWKMember
53
+ include Comparable
54
+
55
+ def initialize(value)
56
+ if value.is_a?(String)
57
+ @value = value
58
+ elsif value.nil?
59
+ raise PraseError, "field #{self.class.name} is required"
60
+ else
61
+ raise ParseError, "require field #{self.class.name} to be String but is '#{value}'"
62
+ end
63
+ end
64
+
65
+ def <=>(other)
66
+ @value <=> String(other)
67
+ end
68
+
69
+ def to_s
70
+ @value
71
+ end
72
+
73
+ def present?
74
+ true
75
+ end
76
+ end
77
+ private_constant :RequiredStringJWKMember
78
+
79
+ Kty = Class.new(RequiredStringJWKMember)
80
+ Use = Class.new(OptionalStringJWKMember)
81
+ Alg = Class.new(OptionalStringJWKMember)
82
+ N = Class.new(OptionalStringJWKMember)
83
+ E = Class.new(OptionalStringJWKMember)
84
+ Kid = Class.new(OptionalStringJWKMember)
85
+ X5T = Class.new(OptionalStringJWKMember)
86
+
87
+ class X5C
88
+ include Enumerable
89
+
90
+ class Certificate
91
+ def initialize(certificate)
92
+ raise ParseError unless certificate.is_a?(String)
93
+ @certificate = certificate
94
+ end
95
+
96
+ def to_s
97
+ @certificate
98
+ end
99
+
100
+ def to_str
101
+ to_s
102
+ end
103
+ end
104
+
105
+ def initialize(certificates)
106
+ if certificates.nil?
107
+ @certificates = nil
108
+ else
109
+ raise ParseError unless certificates.is_a?(Array)
110
+ @certificates = certificates.map { |certificate| Certificate.new(certificate) }
111
+ end
112
+ end
113
+
114
+ def inspect
115
+ "X5C(\n#{@certificates.map { |c| "\t#{c}" }.join(",\n")}\n\t)"
116
+ end
117
+
118
+ def present?
119
+ !@certificates.nil?
120
+ end
121
+
122
+ def each
123
+ return unless present?
124
+ @certificates.each { |cert| yield cert }
125
+ end
126
+ end
127
+
128
+ def initialize(hash)
129
+ raise ParseError unless hash.is_a?(Hash)
130
+ %i(Alg Kty Use X5C N E Kid X5T).each do |field_name|
131
+ field = self.class.const_get(field_name).new(hash[String(field_name).downcase])
132
+ instance_variable_set("@#{String(field_name).downcase}", field)
133
+ end
134
+ end
135
+
136
+ attr_reader :alg, :kty, :use, :x5c, :n, :e, :kid, :x5t
137
+ end
138
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class JWKSetDownloader
4
+ InvalidJWKSetError = Class.new(RuntimeError)
5
+
6
+ class JWKSet
7
+ include Enumerable
8
+
9
+ ParseError = Class.new(RuntimeError)
10
+
11
+ def initialize(hash)
12
+ raise ParseError if hash["keys"].is_a?(Hash)
13
+ @keys = hash["keys"].map { |key| JWK.new(key) }
14
+ end
15
+
16
+ def each
17
+ @keys.each { |key| yield key }
18
+ end
19
+
20
+ def inspect
21
+ "JWKSet(#{@keys.collect(&:inspect).join(", ")})"
22
+ end
23
+ end
24
+ private_constant :JWKSet
25
+
26
+ def initialize(http)
27
+ @http = http
28
+ end
29
+
30
+ def download(url)
31
+ body = @http.get(url)
32
+ json = JSON.parse(body)
33
+ begin
34
+ JWKSet.new(json)
35
+ rescue JWKSet::ParseError
36
+ raise InvalidJWKSetError
37
+ end
38
+ end
39
+ end
40
+ private_constant :JWKSetDownloader
41
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require "json/jwt"
3
+
4
+ class Auth0RS256JWTVerifier
5
+ class JWTDecoder
6
+ def decode(jwt_str)
7
+ jwt = JSON::JWT.decode(jwt_str, :skip_verification)
8
+ DecodedJWT.new(jwt)
9
+ end
10
+
11
+ def signed_with?(jwt_str, public_key)
12
+ JSON::JWT.decode(jwt_str, public_key)
13
+ true
14
+ rescue JSON::JWS::VerificationFailed
15
+ false
16
+ end
17
+
18
+ class DecodedJWT
19
+ def initialize(jwt)
20
+ @jwt = jwt
21
+ end
22
+
23
+ def [](k)
24
+ case k
25
+ when :alg then @jwt.alg
26
+ when :kid then @jwt.header[:kid]
27
+ else @jwt[k]
28
+ end
29
+ end
30
+
31
+ def key?(k)
32
+ ![k].nil?
33
+ end
34
+ end
35
+ private_constant :DecodedJWT
36
+ end
37
+ private_constant :JWTDecoder
38
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class JWTDecoderWrapper
4
+ Payload = Struct.new(:sub)
5
+ private_constant :Payload
6
+
7
+ Error = Class.new(RuntimeError)
8
+ InvalidJWTError = Class.new(Error)
9
+ InvalidAlgError = Class.new(Error)
10
+ InvalidAudienceError = Class.new(Error)
11
+ InvalidIssuerError = Class.new(Error)
12
+ MissingSubError = Class.new(Error)
13
+ InvalidSubError = Class.new(Error)
14
+ VerificationError = Class.new(Error)
15
+ MissingExpError = Class.new(Error)
16
+ InvalidExpError = Class.new(Error)
17
+ JWTExpiredError = Class.new(Error)
18
+ CertNotFoundError = Class.new(Error)
19
+
20
+ def initialize(audience, issuer, certificates, exp_verifier:, jwt_decoder:)
21
+ @audience = audience
22
+ @issuer = issuer
23
+ @certificates = certificates
24
+ @exp_verifier = exp_verifier
25
+ @jwt_decoder = jwt_decoder
26
+ end
27
+
28
+ def decode(jwt_str)
29
+ jwt_str = String(jwt_str)
30
+
31
+ decoded_jwt = raw_decode(jwt_str)
32
+
33
+ verify_alg(decoded_jwt)
34
+
35
+ public_key = find_public_key_for(decoded_jwt)
36
+ verify_is_signed(jwt_str, public_key)
37
+
38
+ # verify JWT
39
+ verify_expiration_time(decoded_jwt)
40
+ verify_audience(decoded_jwt)
41
+ verify_issuer(decoded_jwt)
42
+ verify_sub(decoded_jwt)
43
+
44
+ Payload.new(decoded_jwt[:sub])
45
+ end
46
+
47
+ private
48
+
49
+ def raw_decode(jwt_str)
50
+ @jwt_decoder.decode(jwt_str)
51
+ rescue StandardError => e
52
+ raise InvalidJWTError, e.message
53
+ end
54
+
55
+ def verify_alg(decoded_jwt)
56
+ alg = decoded_jwt[:alg]
57
+ raise InvalidAlgError, "alg should be RS256 but is #{alg}" unless alg == "RS256"
58
+ end
59
+
60
+ def find_public_key_for(decoded_jwt)
61
+ kid = decoded_jwt[:kid]
62
+ @certificates.find(kid).public_key
63
+ rescue CertsSet::NotFoundError => e
64
+ raise CertNotFoundError, e.message
65
+ end
66
+
67
+ def verify_is_signed(jwt_str, public_key)
68
+ raise VerificationError unless @jwt_decoder.signed_with?(jwt_str, public_key)
69
+ rescue StandardError => e
70
+ raise VerificationError, e.message
71
+ end
72
+
73
+ def verify_expiration_time(decoded_jwt)
74
+ verify_exp_exist(decoded_jwt)
75
+ verify_exp_is_int(decoded_jwt)
76
+ raise JWTExpiredError, "jwt expired" if @exp_verifier.expired?(decoded_jwt[:exp])
77
+ end
78
+
79
+ def verify_exp_exist(decoded_jwt)
80
+ raise MissingExpError, "missing 'exp' jwt key" unless decoded_jwt.key?(:exp)
81
+ end
82
+
83
+ def verify_exp_is_int(decoded_jwt)
84
+ return if decoded_jwt[:exp].is_a?(Integer)
85
+ raise InvalidExpError, "jwt 'exp' field must be an integer"
86
+ end
87
+
88
+ def verify_audience(decoded_jwt)
89
+ raise InvalidAudienceError unless Array(decoded_jwt[:aud]).include?(@audience)
90
+ end
91
+
92
+ def verify_issuer(decoded_jwt)
93
+ raise InvalidIssuerError unless decoded_jwt[:iss] == @issuer
94
+ end
95
+
96
+ def verify_sub(decoded_jwt)
97
+ raise MissingSubError unless decoded_jwt.key?(:sub)
98
+ raise InvalidSubError unless decoded_jwt[:sub].is_a?(String)
99
+ end
100
+ end
101
+ private_constant :JWTDecoderWrapper
102
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ module Results
4
+ class Base
5
+ def valid?
6
+ raise NotImplementedMethod
7
+ end
8
+
9
+ def invalid?
10
+ !valid?
11
+ end
12
+
13
+ def on(_)
14
+ raise NotImplementedMethod
15
+ end
16
+ end
17
+ private_constant :Base
18
+
19
+ class ValidAccessToken < Base
20
+ def initialize(user_id)
21
+ @user_id = user_id
22
+ end
23
+
24
+ attr_reader :user_id
25
+
26
+ def valid?
27
+ true
28
+ end
29
+
30
+ def on(type)
31
+ yield @user_id if type == :valid
32
+ self
33
+ end
34
+
35
+ def inspect
36
+ "Auth0RS256JWTVerifier::Results::ValidAccessToken(user_id: #{@user_id})"
37
+ end
38
+ end
39
+
40
+ INVALID_ACCESS_TOKEN = Class.new(Base) do
41
+ def valid?
42
+ false
43
+ end
44
+
45
+ def on(type)
46
+ yield if type == :invalid
47
+ self
48
+ end
49
+
50
+ def inspect
51
+ "Auth0RS256JWTVerifier::Results::INVALID_ACCESS_TOKEN"
52
+ end
53
+ end.new.freeze
54
+ end
55
+ private_constant :Results
56
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class UserId
4
+ def initialize(id)
5
+ @id = String(id).dup.freeze
6
+ end
7
+
8
+ def to_s
9
+ @id
10
+ end
11
+
12
+ def to_str
13
+ to_s
14
+ end
15
+
16
+ def ==(other)
17
+ String(self) == String(other)
18
+ end
19
+
20
+ def inspect
21
+ "Auth0RS256JWTVerifier::UserId(#{@id})"
22
+ end
23
+ end
24
+ private_constant :UserId
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ class Auth0RS256JWTVerifier
3
+ class ValidJWKSet
4
+ include Enumerable
5
+
6
+ def initialize(jwk_set)
7
+ @jwk_set = jwk_set
8
+ end
9
+
10
+ def each
11
+ filtered.each { |jwk| yield jwk }
12
+ end
13
+
14
+ private
15
+
16
+ def filtered
17
+ @filtered ||= @jwk_set.select { |jwk| valid_jwk?(jwk) }
18
+ end
19
+
20
+ def valid_jwk?(jwk)
21
+ jwk.use == "sig" &&
22
+ jwk.kty == "RSA" &&
23
+ jwk.kid.present? &&
24
+ jwk.x5c.any?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ Metrics/ClassLength:
5
+ Enabled: false
6
+
7
+ Metrics/BlockLength:
8
+ Enabled: false
9
+
10
+ RSpec:
11
+ Enabled: false
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class Auth0RS256JWTVerifier
5
+ describe JWTDecoder do
6
+ before :each do
7
+ @decoder = JWTDecoder.new
8
+ end
9
+
10
+ it "should decodde simple jwt" do
11
+ jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImlkMTIzNCJ9." \
12
+ "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiYXVkaWVuY2UiLCJpc3MiO" \
13
+ "iJpc3N1ZXIifQ.cp374RbcG-q8rTLxSoWtLK7dtn5cBa3_g4riKL9OSt0"
14
+ result = @decoder.decode(jwt)
15
+ assert_equal "HS256", result[:alg]
16
+ assert_equal "id1234", result[:kid]
17
+ assert_equal "audience", result[:aud]
18
+ assert_equal "issuer", result[:iss]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class Auth0RS256JWTVerifier
5
+ describe JWTDecoderWrapper do
6
+ class JWTDecoderWrapperFake
7
+ def decode(_jwt)
8
+ {}
9
+ end
10
+
11
+ def signed_with?(_jwt, _public_key)
12
+ true
13
+ end
14
+ end
15
+
16
+ class CertsSetFake
17
+ def find(_id)
18
+ CertFake.new
19
+ end
20
+ end
21
+
22
+ class CertFake
23
+ def public_key
24
+ "ascd"
25
+ end
26
+ end
27
+
28
+ class ExpVerifierFake
29
+ def expired?(_)
30
+ false
31
+ end
32
+ end
33
+
34
+ before :all do
35
+ @decoder = JWTDecoderWrapperFake.new
36
+ @certs_set = CertsSetFake.new
37
+ @exp_verifier = ExpVerifierFake.new
38
+ @audience = "valid audience"
39
+ @issuer = "valid issuer"
40
+
41
+ @jwt_decoder = factory_subject
42
+ end
43
+
44
+ it "should execute JWT#decode & JWT#verify with valid args" do
45
+ jwt_str = "JWT to decode"
46
+ public_key = "valid public key"
47
+ cert = Object.new
48
+ cert.define_singleton_method(:public_key) { public_key }
49
+
50
+ @decoder = Minitest::Mock.new
51
+ @decoder.expect(:decode, valid_decoded_jwt, [jwt_str])
52
+ @decoder.expect(:signed_with?, valid_decoded_jwt, [jwt_str, public_key])
53
+
54
+ @jwt_decoder = factory_subject(jwt_decoder: @decoder)
55
+
56
+ @certs_set.stub(:find, cert) do
57
+ @jwt_decoder.decode(jwt_str)
58
+ end
59
+
60
+ @decoder.verify
61
+ end
62
+
63
+ it "should raise InvalidJWTError if adapter#decode raises standard error" do
64
+ @decoder.stub(:decode, ->(*_) { raise StandardError }) do
65
+ assert_raises(JWTDecoderWrapper::InvalidJWTError) do
66
+ @jwt_decoder.decode("jwt")
67
+ end
68
+ end
69
+ end
70
+
71
+ it "should raise InvalidAlgError" do
72
+ @decoder.stub(
73
+ :decode,
74
+ valid_decoded_jwt(alg: "HS256"),
75
+ ) do
76
+ assert_raises(JWTDecoderWrapper::InvalidAlgError) do
77
+ @jwt_decoder.decode("jwt")
78
+ end
79
+ end
80
+ end
81
+
82
+ it "should raise CertNotFoundError" do
83
+ @decoder.stub(
84
+ :decode,
85
+ valid_decoded_jwt(kid: "unexisting key"),
86
+ ) do
87
+ @certs_set.stub(
88
+ :find,
89
+ ->(*_) { raise CertsSet::NotFoundError },
90
+ ) do
91
+ assert_raises(JWTDecoderWrapper::CertNotFoundError) do
92
+ @jwt_decoder.decode("jwt")
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ it "should raise VerificationError" do
99
+ @decoder.stub(:decode, valid_decoded_jwt) do
100
+ @decoder.stub(:signed_with?, false) do
101
+ assert_raises(JWTDecoderWrapper::VerificationError) do
102
+ @jwt_decoder.decode("jwt")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ it "should raise VerificationError if adapter#signed_with raises StandardError" do
109
+ @decoder.stub(:decode, valid_decoded_jwt) do
110
+ @decoder.stub(:signed_with?, ->(*_) { raise StandardError }) do
111
+ assert_raises(JWTDecoderWrapper::VerificationError) do
112
+ @jwt_decoder.decode("jwt")
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ it "should raise missing exp error" do
119
+ @decoder.stub(
120
+ :decode,
121
+ valid_decoded_jwt.reject { |k, _| k == :exp },
122
+ ) do
123
+ assert_raises(JWTDecoderWrapper::MissingExpError) do
124
+ @jwt_decoder.decode("jwt")
125
+ end
126
+ end
127
+ end
128
+
129
+ it "should raise invalid exp error" do
130
+ @decoder.stub(
131
+ :decode,
132
+ valid_decoded_jwt(exp: "1234"),
133
+ ) do
134
+ assert_raises(JWTDecoderWrapper::InvalidExpError) do
135
+ @jwt_decoder.decode("jwt")
136
+ end
137
+ end
138
+ end
139
+
140
+ it "should raise jwt expired error" do
141
+ @decoder.stub(
142
+ :decode,
143
+ valid_decoded_jwt(exp: 1234),
144
+ ) do
145
+ @exp_verifier.stub(:expired?, true) do
146
+ assert_raises(JWTDecoderWrapper::JWTExpiredError) do
147
+ @jwt_decoder.decode("jwt")
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ it "should raise invalid audience error" do
154
+ @decoder.stub(
155
+ :decode,
156
+ valid_decoded_jwt(aud: "invalid audience"),
157
+ ) do
158
+ assert_raises(JWTDecoderWrapper::InvalidAudienceError) do
159
+ @jwt_decoder.decode("jwt")
160
+ end
161
+ end
162
+ end
163
+
164
+ it "should raise invalid issuer error" do
165
+ @decoder.stub(
166
+ :decode,
167
+ valid_decoded_jwt(iss: "invalid issuer"),
168
+ ) do
169
+ assert_raises(JWTDecoderWrapper::InvalidIssuerError) do
170
+ @jwt_decoder.decode("jwt")
171
+ end
172
+ end
173
+ end
174
+
175
+ it "should raise missing sub error" do
176
+ @decoder.stub(
177
+ :decode,
178
+ valid_decoded_jwt.reject { |k, _| k == :sub },
179
+ ) do
180
+ assert_raises(JWTDecoderWrapper::MissingSubError) do
181
+ @jwt_decoder.decode("jwt")
182
+ end
183
+ end
184
+ end
185
+
186
+ it "should raise invalid sub error" do
187
+ @decoder.stub(
188
+ :decode,
189
+ valid_decoded_jwt(sub: { id: 1 }),
190
+ ) do
191
+ assert_raises(JWTDecoderWrapper::InvalidSubError) do
192
+ @jwt_decoder.decode("jwt")
193
+ end
194
+ end
195
+ end
196
+
197
+ it "should raise nothing and return payload with sub" do
198
+ sub = "sub1234"
199
+ @decoder.stub(
200
+ :decode,
201
+ valid_decoded_jwt(sub: sub),
202
+ ) do
203
+ begin
204
+ payload = @jwt_decoder.decode("jwt")
205
+ assert_respond_to payload, :sub
206
+ assert_equal sub, payload.sub
207
+ rescue StandardError => e
208
+ refute "expected to not return any error buty got #{e}"
209
+ end
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ def factory_subject(dependencies = {})
216
+ default_dependencies = {
217
+ exp_verifier: @exp_verifier,
218
+ jwt_decoder: @decoder,
219
+ }
220
+ JWTDecoderWrapper.new(
221
+ @audience, @issuer, @certs_set,
222
+ default_dependencies.merge(dependencies)
223
+ )
224
+ end
225
+
226
+ def valid_decoded_jwt(overwrites = {})
227
+ {
228
+ alg: "RS256",
229
+ kid: "id1",
230
+ exp: 1234,
231
+ aud: @audience,
232
+ iss: @issuer,
233
+ sub: "1234",
234
+ }.merge(overwrites)
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ describe Auth0RS256JWTVerifier do
5
+ it "verifies successfully access token" do
6
+ auth0 = Auth0RS256JWTVerifier.new(
7
+ issuer: "https://multi-jobbers.eu.auth0.com/",
8
+ audience: "https://multijobbers.herokuapp.com/",
9
+ jwks_url: "https://multi-jobbers.eu.auth0.com/.well-known/jwks.json",
10
+ http: http_stub,
11
+ exp_verifier: exp_verifier_stub,
12
+ )
13
+
14
+ verification_result = auth0.verify(
15
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5rTkJPRFEzUWpNeFF6WkVOa1" \
16
+ "kzUVVNMk9VTTFSVGMxUTBZMk4wUXdSVGRHTWpkRk9UQkROdyJ9.eyJpc3MiOiJodHRwczo" \
17
+ "vL211bHRpLWpvYmJlcnMuZXUuYXV0aDAuY29tLyIsInN1YiI6IjZwM2tFc0pkOUhteGFxV" \
18
+ "VIwdXN3c1VFRUdoZTNuQ05IQGNsaWVudHMiLCJhdWQiOiJodHRwczovL211bHRpam9iYmV" \
19
+ "ycy5oZXJva3VhcHAuY29tLyIsImV4cCI6MTQ5NzE4MTMzNSwiaWF0IjoxNDk3MDk0OTM1L" \
20
+ "CJzY29wZSI6IiJ9.LPtyDb26UxS5NKHEU2VJdmj7pDI4-tfgue2Ttk62H0a9XehsCArwwy" \
21
+ "QtI2ZAXxY8gQGS4dhXpcDqevmpfAy9zcjMEvvjWqmcGpepL8bn4MUJ_lAmL3A3FJXduf8T" \
22
+ "pHRXUHiMcdGT0vcFrv5kkMHDzTiwvOUxPcRT5nufX16Vqg3MTQS5pDb2NPcLCqI4PrJhse" \
23
+ "uJnDthxYelUvf6AIyVesuK5e3g8FLiXjZoPmwr3u6xeljF2KECetBPskKI8MgWrhIDD9Zv" \
24
+ "-O_fV1UZ41M-7zURcsQNYV--knHuX0i6nF46JlnbdqoA35d8LJvtnzbiO7hj8mP_GMa8FS" \
25
+ "cKqq5D8w",
26
+ )
27
+
28
+ expected_user_id = "6p3kEsJd9HmxaqUR0uswsUEEGhe3nCNH@clients"
29
+
30
+ assert verification_result.valid?
31
+ refute verification_result.invalid?
32
+ assert_equal expected_user_id, verification_result.user_id
33
+ end
34
+
35
+ it "fails when jwt public key verification is not successful" do
36
+ auth0 = Auth0RS256JWTVerifier.new(
37
+ issuer: "https://example.eu.auth0.com/",
38
+ audience: "https://multijobbers.herokuapp.com/",
39
+ jwks_url: "https://multi-jobbers.eu.auth0.com/.well-known/jwks.json",
40
+ http: http_stub,
41
+ exp_verifier: exp_verifier_stub,
42
+ )
43
+
44
+ verification_result = auth0.verify(
45
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5rTkJPRFEzUWpNeFF6WkVOa1" \
46
+ "kzUVVNMk9VTTFSVGMxUTBZMk4wUXdSVGRHTWpkRk9UQkROdyJ9.eyJpc3MiOiJodHRwczo" \
47
+ "vL211bHRpLWpvYmJlcnMuZXUuYXV0aDAuY29tLyIsInN1YiI6IjZwM2tFc0pkOUhteGFxV" \
48
+ "VIwdXN3c1VFRUdoZTNuQ05IQGNsaWVudHMiLCJhdWQiOiJodHRwczovL211bHRpam9iYmV" \
49
+ "ycy5oZXJva3VhcHAuY29tLyIsImV4cCI6MTQ5NzQ3NzYyOCwiaWF0IjoxNDk3MzkxMjI4L" \
50
+ "CJzY29wZSI6IiJ9.aGhyzkMM7sE4FNSijzRJlIvJQwx4tBq8uJbL0Taq9I41YOuSWPF4eC" \
51
+ "8886EU3gLkOiEpYlSkX9SHINljHR2ajcNBXoCThbREyReY_ZDjNfXZRREYWT6x8wT5WtmM" \
52
+ "xdxpOOVIXKrxkhfy57vGJs2clpo2MTFEVNPYhslMv-p_WLY",
53
+ )
54
+
55
+ refute verification_result.valid?
56
+ assert verification_result.invalid?
57
+ end
58
+
59
+ it "fails when jwt is expired" do
60
+ auth0 = Auth0RS256JWTVerifier.new(
61
+ issuer: "https://multi-jobbers.eu.auth0.com/",
62
+ audience: "https://multijobbers.herokuapp.com/",
63
+ jwks_url: "https://multi-jobbers.eu.auth0.com/.well-known/jwks.json",
64
+ http: http_stub,
65
+ exp_verifier: exp_verifier_stub(expired: true),
66
+ )
67
+
68
+ verification_result = auth0.verify(
69
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5rTkJPRFEzUWpNeFF6WkVOa1" \
70
+ "kzUVVNMk9VTTFSVGMxUTBZMk4wUXdSVGRHTWpkRk9UQkROdyJ9.eyJpc3MiOiJodHRwczo" \
71
+ "vL211bHRpLWpvYmJlcnMuZXUuYXV0aDAuY29tLyIsInN1YiI6IjZwM2tFc0pkOUhteGFxV" \
72
+ "VIwdXN3c1VFRUdoZTNuQ05IQGNsaWVudHMiLCJhdWQiOiJodHRwczovL211bHRpam9iYmV" \
73
+ "ycy5oZXJva3VhcHAuY29tLyIsImV4cCI6MTQ5NzE4MTMzNSwiaWF0IjoxNDk3MDk0OTM1L" \
74
+ "CJzY29wZSI6IiJ9.LPtyDb26UxS5NKHEU2VJdmj7pDI4-tfgue2Ttk62H0a9XehsCArwwy" \
75
+ "QtI2ZAXxY8gQGS4dhXpcDqevmpfAy9zcjMEvvjWqmcGpepL8bn4MUJ_lAmL3A3FJXduf8T" \
76
+ "pHRXUHiMcdGT0vcFrv5kkMHDzTiwvOUxPcRT5nufX16Vqg3MTQS5pDb2NPcLCqI4PrJhse" \
77
+ "uJnDthxYelUvf6AIyVesuK5e3g8FLiXjZoPmwr3u6xeljF2KECetBPskKI8MgWrhIDD9Zv" \
78
+ "-O_fV1UZ41M-7zURcsQNYV--knHuX0i6nF46JlnbdqoA35d8LJvtnzbiO7hj8mP_GMa8FS" \
79
+ "cKqq5D8w",
80
+ )
81
+
82
+ refute verification_result.valid?
83
+ assert verification_result.invalid?
84
+ end
85
+
86
+ it "fails when jwt is random string" do
87
+ auth0 = Auth0RS256JWTVerifier.new(
88
+ issuer: "https://multi-jobbers.eu.auth0.com/",
89
+ audience: "https://multijobbers.herokuapp.com/",
90
+ jwks_url: "https://multi-jobbers.eu.auth0.com/.well-known/jwks.json",
91
+ http: http_stub,
92
+ exp_verifier: exp_verifier_stub(expired: true),
93
+ )
94
+
95
+ verification_result = auth0.verify("random string")
96
+
97
+ refute verification_result.valid?
98
+ assert verification_result.invalid?
99
+ end
100
+
101
+ private
102
+
103
+ def http_stub
104
+ @http_stub = Object.new
105
+ jwks = sample_jwks
106
+ @http_stub.define_singleton_method(:get) { |*_| jwks }
107
+ @http_stub
108
+ end
109
+
110
+ def exp_verifier_stub(expired: false)
111
+ @exp_verifier_stub = Object.new
112
+ @exp_verifier_stub.define_singleton_method(:expired?) { |*_| expired }
113
+ @exp_verifier_stub
114
+ end
115
+
116
+ def sample_jwks
117
+ @sample_jwsk ||= File.read("./test/fixtures/sample_jwks.json")
118
+ end
119
+ end
@@ -0,0 +1,16 @@
1
+ {
2
+ "keys":[
3
+ {
4
+ "alg":"RS256",
5
+ "kty":"RSA",
6
+ "use":"sig",
7
+ "x5c":[
8
+ "MIIDDzCCAfegAwIBAgIJXBxlkRndNs3ZMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNVBAMTGm11bHRpLWpvYmJlcnMuZXUuYXV0aDAuY29tMB4XDTE3MDUyOTEzNTA1OFoXDTMxMDIwNTEzNTA1OFowJTEjMCEGA1UEAxMabXVsdGktam9iYmVycy5ldS5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNwGcDVRbVjAXEhmMfa8OT37YAcFMhCGDZSgORTg/Wo+3E+WipgckLpT6+7LZH1ohgcbEVKkcDxjW0djYkSmgVvcDOxKClzj2iwXM0FYXJtU6Vwq1R5BzKWeMclbCZT7gEpVkCte3ZFvY80lYwlGvrw5QLMURnW5uMWA75Hf2VhzVx07M1KcwL3reHLd5YqDq5mrwTr+g5maFvl+t2dcMzOwuI2LhKgdImDGR6BUEteeEchNeir1r1lBrd5juwPiXT76ZVuGYnzf01UPqhGfcjSKey4W8WbIiYpDm7J0ORjGm/HAMHSvnOTYIqrJ62J0p5PexYzUGOhH0N06/G+VgtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFInchclYauappYScpEgOo+hUFwdZMA4GA1UdDwEB/wQEAwIChDANBgkqhkiG9w0BAQsFAAOCAQEAoJ/uqy6TYmWMnuVP1vHQDv8pFRv5mpXa8D6aqDzfXKW3P4pAdFfDZ5PFVZkSiAnd/DJk//9cVAT7SEHHe9yxkp8N6MewxdHT+JGAszG4xyR73eV7gCAcITGh3t1efflNcxV7ycTJp61FuPtVs7MxdUAgWkpP5nMd41pHY01r5qy1g+ysUJ5TDI5SmqEAFc54etX5i8WUDtcH0DxJrib+YlZPA7xrbCV5DnykODXjJ0De7J89hcIXq4eL3TtbPCqJMPwH79cphJ9NUd+320WexTdNB5ysL7ML/Uk1KaWuS7fyA0C+gxSF6gOaJ3/u2OE3TrudLdTco/C87YnvWYJS9A=="
9
+ ],
10
+ "n":"zcBnA1UW1YwFxIZjH2vDk9-2AHBTIQhg2UoDkU4P1qPtxPloqYHJC6U-vuy2R9aIYHGxFSpHA8Y1tHY2JEpoFb3AzsSgpc49osFzNBWFybVOlcKtUeQcylnjHJWwmU-4BKVZArXt2Rb2PNJWMJRr68OUCzFEZ1ubjFgO-R39lYc1cdOzNSnMC963hy3eWKg6uZq8E6_oOZmhb5frdnXDMzsLiNi4SoHSJgxkegVBLXnhHITXoq9a9ZQa3eY7sD4l0--mVbhmJ839NVD6oRn3I0insuFvFmyImKQ5uydDkYxpvxwDB0r5zk2CKqyetidKeT3sWM1BjoR9DdOvxvlYLQ",
11
+ "e":"AQAB",
12
+ "kid":"NkNBODQ3QjMxQzZENkY3QUM2OUM1RTc1Q0Y2N0QwRTdGMjdFOTBDNw",
13
+ "x5t":"NkNBODQ3QjMxQzZENkY3QUM2OUM1RTc1Q0Y2N0QwRTdGMjdFOTBDNw"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1 @@
1
+ {"keys":[{"alg":"RS256","kty":"RSA","use":"sig","x5c":["MIIDETCCAfmgAwIBAgIJALUn2x16sU0BMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNVBAMMFGV4YW1wbGUuZXUuYXV0aDAuY29tMB4XDTE1MDQyMzA3MTcyOVoXDTI4MTIzMDA3MTcyOVowHzEdMBsGA1UEAwwUZXhhbXBsZS5ldS5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQColCyrzR24viQYf3gdHSrH1rCMS7wH3SOmzq5RRZeOxz6wcmTr8Jip/SQiS5QSsFfUoRk8PPbeafUhF+/NDwvCJXnUf8lTDeOAVRDM5hkfWnLZf8sBHYdAZXTps6Oz6Nq0MT4J/7cL43a1Q/UU8qwCYG652NPX2bPIAjfxq28MUJ47iz2EKg445MHpsuHU3MXqKApyLBlyUQL4VRw9xjlqkL45HTB2zCNuO4o8zQNE70jWQe8b4eauzLT5oeakUVTW5vGq5ryKE6T0vUERxYO/Bxzw+qfZ75IV9dQefUZ41WLB6PWP6OvzsRSEOGZR2LBMh3IfArhUwNxpkmTf4lZfAgMBAAGjUDBOMB0GA1UdDgQWBBShBM7FGFBWlZSI0AkqeI/tcZftQDAfBgNVHSMEGDAWgBShBM7FGFBWlZSI0AkqeI/tcZftQDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCOraDmW3eSauYY4Xdf81t7XgPiUiWaqHUOtLEFOVmpPlX8mHtcUpSHZwh2C52iLtFkBUq97r2AwmTArysTb+ZNsFotEaAxTe2f6dO8oV7lxjn5ApL3NZWVDXsO//F/kJTsA0AlGozdHedb/Xy90atY9IRBHqlY49QHJmiheSxy3QTuABEVfHShsbqhT22a/VOEWelwyJZyeQX03ysu6c9NqnWblU/9j/Ccg96VorL7bYN0dLgImdcZQFR8fjhGeV86n15C7ZvOE4cJyVdBc0er1IrEmx7oXG6YgxyBYMRL+DiIjZAftE6YjOuet2GQOSdGYPiYqn6Z7xLg4DPTaWEP"],"n":"qJQsq80duL4kGH94HR0qx9awjEu8B90jps6uUUWXjsc-sHJk6_CYqf0kIkuUErBX1KEZPDz23mn1IRfvzQ8LwiV51H_JUw3jgFUQzOYZH1py2X_LAR2HQGV06bOjs-jatDE-Cf-3C-N2tUP1FPKsAmBuudjT19mzyAI38atvDFCeO4s9hCoOOOTB6bLh1NzF6igKciwZclEC-FUcPcY5apC-OR0wdswjbjuKPM0DRO9I1kHvG-Hmrsy0-aHmpFFU1ubxqua8ihOk9L1BEcWDvwcc8Pqn2e-SFfXUHn1GeNViwej1j-jr87EUhDhmUdiwTIdyHwK4VMDcaZJk3-JWXw","e":"AQAB","kid":"QUVGRUVENTEwNDUwODlFQjA1QzE0QkVBMUY5NDFFRjFBRjI5Mzc3MA","x5t":"QUVGRUVENTEwNDUwODlFQjA1QzE0QkVBMUY5NDFFRjFBRjI5Mzc3MA"}]}
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require "minitest/autorun"
3
+ require "auth0_rs256_jwt_verifier"
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auth0_rs256_jwt_verifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Krzysztof Zielonka
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json-jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5'
69
+ description: Auth0 (https://auth0.com) is web service handling users identities which
70
+ can be easily plugged into your application. It provides SDKs for many languages
71
+ which enable you to sign up/in users and returns access token (JWT) in exchange.
72
+ Access token can be used then to access your's Web Service. This gem helps you to
73
+ verify (https://auth0.com/docs/api-auth/tutorials/verify-access-token#verify-the-signature)
74
+ such access token which has been signed using the RS256 algorithm.
75
+ email: krzysztof.zielonka@droidsonroids.pl
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - ".gitignore"
81
+ - ".rubocop.yml"
82
+ - Gemfile
83
+ - README.md
84
+ - Rakefile
85
+ - auth0_rs256_jwt_verifier.gemspec
86
+ - lib/auth0_access_tokens_rs256_verifier.rb
87
+ - lib/auth0_rs256_jwt_verifier.rb
88
+ - lib/auth0_rs256_jwt_verifier/certs_set.rb
89
+ - lib/auth0_rs256_jwt_verifier/exp_verifier.rb
90
+ - lib/auth0_rs256_jwt_verifier/jwk.rb
91
+ - lib/auth0_rs256_jwt_verifier/jwk_set_downloader.rb
92
+ - lib/auth0_rs256_jwt_verifier/jwt_decoder.rb
93
+ - lib/auth0_rs256_jwt_verifier/jwt_decoder_wrapper.rb
94
+ - lib/auth0_rs256_jwt_verifier/results.rb
95
+ - lib/auth0_rs256_jwt_verifier/user_id.rb
96
+ - lib/auth0_rs256_jwt_verifier/valid_jwk_set.rb
97
+ - test/.rubocop.yml
98
+ - test/auth0_rs256_jwt_verifier/jwt_decoder_test.rb
99
+ - test/auth0_rs256_jwt_verifier/jwt_decoder_wrapper_test.rb
100
+ - test/auth0_rs256_jwt_verifier_test.rb
101
+ - test/fixtures/sample_jwks.json
102
+ - test/fixtures/sample_jwks_2.json
103
+ - test/test_helper.rb
104
+ homepage: https://rubygems.org/gems/auth0_rs256_jwt_verifier
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.6.8
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Auth0 JWT (RS256) verification library
128
+ test_files: []