auth0_rs256_jwt_verifier 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []