warden_openid_bearer 0.1.4 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +13 -5
- data/lib/warden_openid_bearer/cache_mixin.rb +21 -16
- data/lib/warden_openid_bearer/discovered_config.rb +10 -10
- data/lib/warden_openid_bearer/net_https.rb +34 -0
- data/lib/warden_openid_bearer/strategy.rb +41 -50
- data/lib/warden_openid_bearer/version.rb +1 -1
- data/lib/warden_openid_bearer.rb +3 -0
- metadata +5 -19
- data/warden_openid_bearer.gemspec +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3185e14bde01f3b44bae689e3942051973e8a38ee25eb382c549a87175a80597
|
4
|
+
data.tar.gz: 00d8d47d1f1656ac9dafd8257b818c80b32ca3df22d63895b50765cabc29d199
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15407ea9f324dca19089df0862d150a303fe695313c3e1aad83dc023c824a6bcdbaa367e182c4f86014d8199d6341e9d62ec75b362e87c2a2e9ab41b742512a0
|
7
|
+
data.tar.gz: 80efe7bdd1fb90077dd74718144b5c014b165b04395ae3d8e4e54894e49436a211c844e74319cf4342b192cc1e6c024835ed4994147f1ef96d9a191389820942
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## [0.2.1] - 2023-11-02
|
2
|
+
- Rewritten to *not* depend on the auth token being JWT (an assumption which only works with Keycloak)
|
3
|
+
- Support user-configured (bogus) certificate for development
|
4
|
+
|
5
|
+
N.B.: 0.2.0 only differs from 0.2.1 by the fact that `rake standard:fix` ran inbetweeen both.
|
6
|
+
|
1
7
|
## [0.1.4] - 2022-10-11
|
2
8
|
- Clean up a stray `puts` left when debugging
|
3
9
|
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# WardenOpenidBearer
|
2
2
|
|
3
|
-
[Warden](https://github.com/wardencommunity/warden) strategy for authentication with OpenID-Connect
|
3
|
+
[Warden](https://github.com/wardencommunity/warden) strategy for authentication with OpenID-Connect bearer tokens.
|
4
4
|
|
5
5
|
This gem is like
|
6
6
|
[the `warden_openid_auth gem`](https://rubygems.org/gems/warden_openid_auth),
|
7
7
|
except that it only provides support for the very last step of
|
8
8
|
the OAuth code flow, i.e. when the resource server / relying party
|
9
|
-
(your Ruby Web app) validates and decodes the
|
9
|
+
(your Ruby Web app) validates and decodes the bearer token.
|
10
10
|
|
11
11
|
Use this gem if your client-side Web (or mobile) app will be taking
|
12
12
|
care of the rest of the OAuth2 motions, such as redirecting (or
|
@@ -26,6 +26,14 @@ with iframes, etc.
|
|
26
26
|
manager.default_strategies WardenOpenidBearer::Strategy.register!
|
27
27
|
WardenOpenidBearer.configure do |oidc|
|
28
28
|
oidc.openid_metadata_url = "https://example.com/.well-known/openid-configuration"
|
29
|
+
oidc.scope = ["openid", "email"]
|
30
|
+
oidc.redirect_uri = ["openid", "email"]
|
31
|
+
# Optional — Explicit OpenID-Connect server certificate (e.g. for a development rig):
|
32
|
+
oidc.openid_server_certificate = <<-CERT
|
33
|
+
-----BEGIN CERTIFICATE-----
|
34
|
+
MIIDCTBLAHBLAHBLAH==
|
35
|
+
-----END CERTIFICATE-----
|
36
|
+
CERT
|
29
37
|
end
|
30
38
|
|
31
39
|
manager.failure_app = Proc.new { |_env|
|
@@ -45,7 +53,7 @@ with iframes, etc.
|
|
45
53
|
### Subclassing
|
46
54
|
|
47
55
|
Subclassing `WardenOpenidBearer::Strategy` is the recommended way to
|
48
|
-
- support more than one authentication server (overriding `metadata_url` and/or `cache_timeout`),
|
56
|
+
- support more than one authentication server (overriding `valid?`, `metadata_url` and/or `cache_timeout`),
|
49
57
|
- provide user hydration into the class of your choice (overriding `user_of_claims`).
|
50
58
|
|
51
59
|
More details available in the rubydoc comments of
|
@@ -69,7 +77,7 @@ After checking out the Git repository, run `bin/setup` to install dependencies.
|
|
69
77
|
|
70
78
|
The `debugger` gem is a development-time requirement (in the Gemfile). In order to activate it:
|
71
79
|
|
72
|
-
1. Uncomment the line that says `require "debug"` in `./spec_helper.rb`
|
80
|
+
1. Uncomment the line that says `require "debug"` in `./spec/spec_helper.rb`
|
73
81
|
1. Stick `debugger` somewhere in the source or test code
|
74
82
|
1. Run the test suite
|
75
83
|
|
@@ -87,7 +95,7 @@ To release a new version:
|
|
87
95
|
|
88
96
|
## Contributing
|
89
97
|
|
90
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/epfl-si/warden_openid_bearer.
|
98
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/epfl-si/warden_openid_bearer .
|
91
99
|
|
92
100
|
## License
|
93
101
|
|
@@ -4,31 +4,36 @@ module WardenOpenidBearer
|
|
4
4
|
# We don't need an overengineered approach based on the Rails cache.
|
5
5
|
# No, really.
|
6
6
|
module CacheMixin
|
7
|
-
def cached_by(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
7
|
+
def cached_by(*keys, &do_it)
|
8
|
+
@__cache_mixin__cache ||= {}
|
9
|
+
|
10
|
+
caller_method = caller(1..1).first[/`.*'/][1..-2]
|
11
|
+
keys.unshift(caller_method)
|
12
|
+
|
13
|
+
first_keys = keys.slice!(0, keys.length - 1).join("|")
|
14
|
+
last_key = keys[0]
|
15
|
+
|
16
|
+
last_key_is_value_type = last_key.is_a? String
|
17
|
+
cache = @__cache_mixin__cache[first_keys] ||= if last_key_is_value_type
|
18
|
+
{}
|
19
|
+
else
|
20
|
+
# Use the ::ObjectSpace::WeakMap private API, because the
|
21
|
+
# endeavor of reinventing weak maps on top of (public)
|
22
|
+
# WeakRef's would be called an inversion of abstraction and
|
23
|
+
# would be considered harmful. Sue me (I have unit tests).
|
24
|
+
::ObjectSpace::WeakMap.new
|
25
|
+
end
|
21
26
|
|
22
27
|
now = Time.now()
|
23
28
|
|
24
|
-
if (cached = cache[
|
29
|
+
if (cached = cache[last_key])
|
25
30
|
unless respond_to?(:cache_timeout) && now - cached[:fetched_at] > cache_timeout
|
26
31
|
return cached[:payload]
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
30
35
|
retval = do_it.call
|
31
|
-
cache[
|
36
|
+
cache[last_key] = {payload: retval, fetched_at: now}
|
32
37
|
retval
|
33
38
|
end
|
34
39
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "net/http"
|
4
|
+
require "warden_openid_bearer/net_https"
|
4
5
|
|
5
6
|
module WardenOpenidBearer
|
6
7
|
# Cacheable configuration (periodically re-)fetched starting from
|
@@ -19,17 +20,11 @@ module WardenOpenidBearer
|
|
19
20
|
# Provide a public API for tuning the timeout.
|
20
21
|
attr_writer :cache_timeout
|
21
22
|
|
22
|
-
def
|
23
|
-
|
23
|
+
def userinfo_endpoint
|
24
|
+
metadata[:userinfo_endpoint]
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
metadata[:issuer]
|
28
|
-
end
|
29
|
-
|
30
|
-
def authorization_algs
|
31
|
-
metadata[:authorization_signing_alg_values_supported]
|
32
|
-
end
|
27
|
+
attr_writer :peer_cert
|
33
28
|
|
34
29
|
private
|
35
30
|
|
@@ -39,7 +34,12 @@ module WardenOpenidBearer
|
|
39
34
|
|
40
35
|
def json(uri)
|
41
36
|
cached_by(uri) do
|
42
|
-
|
37
|
+
response = if uri.scheme == "https"
|
38
|
+
WardenOpenidBearer::NetHTTPS.get_response(URI(uri), @peer_cert)
|
39
|
+
else
|
40
|
+
Net::HTTP.get_response(URI(uri))
|
41
|
+
end
|
42
|
+
JSON.parse(response.body, symbolize_names: true)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
module WardenOpenidBearer
|
6
|
+
# Like Net::HTTP, but with TLS and VERIFY_PEER always on.
|
7
|
+
class NetHTTPS < Net::HTTP
|
8
|
+
def initialize(*things)
|
9
|
+
super(*things)
|
10
|
+
self.use_ssl = true
|
11
|
+
self.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
12
|
+
end
|
13
|
+
|
14
|
+
def peer_cert=(peer_cert)
|
15
|
+
self.verify_hostname = false
|
16
|
+
self.verify_callback = lambda do |preverify_ok, cert_store|
|
17
|
+
end_cert_der = cert_store.chain[0].to_der
|
18
|
+
return preverify_ok unless end_cert_der == cert_store.current_cert.to_der
|
19
|
+
|
20
|
+
return end_cert_der == peer_cert.to_der
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_response(uri, peer_cert = nil)
|
25
|
+
https = new(uri.hostname, uri.port)
|
26
|
+
https.peer_cert = peer_cert if peer_cert
|
27
|
+
|
28
|
+
req = Net::HTTP::Get.new(uri)
|
29
|
+
https.start do |https|
|
30
|
+
https.request(req)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "uri"
|
4
|
+
require "net/http"
|
5
|
+
require "warden_openid_bearer/net_https"
|
4
6
|
|
5
7
|
module WardenOpenidBearer
|
6
8
|
# Like `WardenOpenidAuth::Strategy` in
|
7
9
|
# `lib/warden_openid_auth/strategy.rb` from the `warden_openid_auth`
|
8
10
|
# gem, except done right for a modern, split-backend Web application
|
9
11
|
# (in which the browser takes charge of the OAuth2 login dance, and
|
10
|
-
# the back-end only
|
12
|
+
# the back-end only validates bearer tokens).
|
11
13
|
#
|
12
14
|
# You shoud subclass `WardenOpenidBearer::Strategy` and override the
|
13
15
|
# `user_of_claims` protected method if you want `env['warden'].user`
|
@@ -24,32 +26,28 @@ module WardenOpenidBearer
|
|
24
26
|
include WardenOpenidBearer::Registerer # Provides self.register!
|
25
27
|
include WardenOpenidBearer::CacheMixin
|
26
28
|
|
29
|
+
# Override in a subclass to support multiple authentication
|
30
|
+
# servers (if tokens can be discriminated between them somehow).
|
31
|
+
# The base class returns True whenever an `Authentication: Bearer`
|
32
|
+
# request header is present.
|
27
33
|
def valid?
|
28
|
-
|
29
|
-
# Do the issuer check here, so as to seamlessly support multiple
|
30
|
-
# OIDC issuers inside the same app. If a token is not “for us”,
|
31
|
-
# we want to defer to the other Warden strategy instances in the
|
32
|
-
# stack (one which could typically be another instance of either
|
33
|
-
# WardenOpenidBearer::Strategy, or one of its subclasses); therefore, we
|
34
|
-
# want to return `false` if issuers don't match.
|
35
|
-
untrusted_issuer == config.issuer
|
34
|
+
!!token
|
36
35
|
end
|
37
36
|
|
38
37
|
def authenticate!
|
39
|
-
|
40
|
-
|
38
|
+
res = oauth2_userinfo_response
|
39
|
+
body = res.body
|
40
|
+
|
41
|
+
if res.is_a?(Net::HTTPSuccess)
|
42
|
+
success! user_of_claims(JSON.parse(body))
|
41
43
|
else
|
42
|
-
|
43
|
-
# we know the status with precision:
|
44
|
-
fail! "Invalid OIDC bearer token"
|
44
|
+
fail! body
|
45
45
|
end
|
46
|
-
rescue JWT::ExpiredSignature
|
47
|
-
fail! "Expired OIDC bearer token"
|
48
46
|
end
|
49
47
|
|
50
48
|
# Overridden to always return false, because we typically *don't*
|
51
49
|
# want persistent sessions for an OpenID-Connect resource server —
|
52
|
-
#
|
50
|
+
# If we cached, we would break logout.
|
53
51
|
def store?
|
54
52
|
false
|
55
53
|
end
|
@@ -57,7 +55,12 @@ module WardenOpenidBearer
|
|
57
55
|
# Made public so that one may tune the `strategy.config.cache_timeout`:
|
58
56
|
def config
|
59
57
|
return @config if @config
|
58
|
+
|
60
59
|
@config = WardenOpenidBearer::DiscoveredConfig.new(metadata_url)
|
60
|
+
if (peer_cert = WardenOpenidBearer.config.openid_server_certificate)
|
61
|
+
@config.peer_cert = peer_cert
|
62
|
+
end
|
63
|
+
|
61
64
|
@config.cache_timeout = cache_timeout
|
62
65
|
@config
|
63
66
|
end
|
@@ -94,7 +97,7 @@ module WardenOpenidBearer
|
|
94
97
|
WardenOpenidBearer.config.cache_timeout
|
95
98
|
end
|
96
99
|
|
97
|
-
# Returns the
|
100
|
+
# Returns the bearer token from `request.headers['Authorization']`
|
98
101
|
# (which may or may not be valid)
|
99
102
|
def token
|
100
103
|
# We call this one quite a lot, so we want some caching. Also,
|
@@ -107,40 +110,28 @@ module WardenOpenidBearer
|
|
107
110
|
end
|
108
111
|
end
|
109
112
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
JWT.decode(token, nil, true, jwt_decode_opts).first
|
115
|
-
end
|
116
|
-
|
117
|
-
def jwt_decode_opts
|
118
|
-
# Note: issuer check was already done in `valid?`, see
|
119
|
-
# explanations there; skip it here.
|
120
|
-
{
|
121
|
-
algorithm: algorithm,
|
122
|
-
verify_expiration: true,
|
123
|
-
verify_not_before: true,
|
124
|
-
verify_iat: true,
|
125
|
-
jwks: config.jwks
|
126
|
-
}
|
127
|
-
end
|
128
|
-
|
129
|
-
def algorithm
|
130
|
-
return untrusted_algorithm if
|
131
|
-
config.authorization_algs.member? untrusted_algorithm
|
132
|
-
end
|
133
|
-
|
134
|
-
def untrusted_fields
|
135
|
-
JWT.decode(token, nil, false)
|
113
|
+
def oauth2_userinfo_response
|
114
|
+
cached_by(request) do
|
115
|
+
_do_oauth2_userinfo
|
116
|
+
end
|
136
117
|
end
|
137
118
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
119
|
+
def _do_oauth2_userinfo
|
120
|
+
uri = URI.parse(config.userinfo_endpoint)
|
121
|
+
req = Net::HTTP::Get.new(uri)
|
122
|
+
req["Authorization"] = "Bearer #{token}"
|
141
123
|
|
142
|
-
|
143
|
-
|
124
|
+
if uri.scheme == "https"
|
125
|
+
http = WardenOpenidBearer::NetHTTPS.new(uri.hostname, uri.port)
|
126
|
+
if (peer_cert = WardenOpenidBearer.config.openid_server_certificate)
|
127
|
+
http.peer_cert = peer_cert
|
128
|
+
end
|
129
|
+
else
|
130
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
131
|
+
end
|
132
|
+
http.start do |http|
|
133
|
+
http.request(req)
|
134
|
+
end
|
144
135
|
end
|
145
136
|
end
|
146
137
|
end
|
data/lib/warden_openid_bearer.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/configurable"
|
4
|
+
require "openssl"
|
5
|
+
require "uri"
|
4
6
|
|
5
7
|
require_relative "warden_openid_bearer/version"
|
6
8
|
require_relative "warden_openid_bearer/registerer"
|
@@ -12,5 +14,6 @@ module WardenOpenidBearer
|
|
12
14
|
extend Dry::Configurable
|
13
15
|
|
14
16
|
setting :openid_metadata_url, constructor: ->(url) { URI(url) }
|
17
|
+
setting :openid_server_certificate, default: nil, constructor: ->(pem) { pem ? OpenSSL::X509::Certificate.new(pem) : nil }
|
15
18
|
setting :cache_timeout, default: 900
|
16
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: warden_openid_bearer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominique Quatravaux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: warden
|
@@ -52,26 +52,12 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.2.2
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: jwt
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '2.5'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '2.5'
|
69
55
|
description: |2+
|
70
56
|
|
71
57
|
This gem is like the `warden_openid_auth` gem, except that it only
|
72
58
|
provides support for the very last step of the OAuth code flow, i.e.
|
73
59
|
when the resource server / relying party (your Ruby Web app)
|
74
|
-
validates
|
60
|
+
validates the bearer token.
|
75
61
|
|
76
62
|
Use this gem if your client-side Web (or mobile) app will be taking
|
77
63
|
care of the rest of the OAuth2 motions, such as redirecting (or
|
@@ -95,11 +81,11 @@ files:
|
|
95
81
|
- lib/warden_openid_bearer.rb
|
96
82
|
- lib/warden_openid_bearer/cache_mixin.rb
|
97
83
|
- lib/warden_openid_bearer/discovered_config.rb
|
84
|
+
- lib/warden_openid_bearer/net_https.rb
|
98
85
|
- lib/warden_openid_bearer/registerer.rb
|
99
86
|
- lib/warden_openid_bearer/strategy.rb
|
100
87
|
- lib/warden_openid_bearer/version.rb
|
101
88
|
- sig/warden_openid_bearer.rbs
|
102
|
-
- warden_openid_bearer.gemspec
|
103
89
|
homepage: https://github.com/epfl-si/warden_openid_bearer
|
104
90
|
licenses:
|
105
91
|
- MIT
|
@@ -123,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
109
|
- !ruby/object:Gem::Version
|
124
110
|
version: '0'
|
125
111
|
requirements: []
|
126
|
-
rubygems_version: 3.3.
|
112
|
+
rubygems_version: 3.3.26
|
127
113
|
signing_key:
|
128
114
|
specification_version: 4
|
129
115
|
summary: Warden strategy to validate OpenID-Connect bearer tokens
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "lib/warden_openid_bearer/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "warden_openid_bearer"
|
7
|
-
spec.version = WardenOpenidBearer::VERSION
|
8
|
-
spec.authors = ["Dominique Quatravaux"]
|
9
|
-
spec.email = ["dominique.quatravaux@epfl.ch"]
|
10
|
-
|
11
|
-
spec.summary = "Warden strategy to validate OpenID-Connect bearer tokens"
|
12
|
-
spec.description = <<~END_DESCRIPTION
|
13
|
-
|
14
|
-
This gem is like the `warden_openid_auth` gem, except that it only
|
15
|
-
provides support for the very last step of the OAuth code flow, i.e.
|
16
|
-
when the resource server / relying party (your Ruby Web app)
|
17
|
-
validates and decodes the JWT token.
|
18
|
-
|
19
|
-
Use this gem if your client-side Web (or mobile) app will be taking
|
20
|
-
care of the rest of the OAuth2 motions, such as redirecting (or
|
21
|
-
opening a popup window) to the authentication server at login time,
|
22
|
-
managing and refreshing tokens, doing all these unspeakable things
|
23
|
-
with iframes, etc.
|
24
|
-
|
25
|
-
END_DESCRIPTION
|
26
|
-
spec.homepage = "https://github.com/epfl-si/warden_openid_bearer"
|
27
|
-
spec.license = "MIT"
|
28
|
-
spec.required_ruby_version = ">= 2.6.0"
|
29
|
-
|
30
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
31
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
32
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
33
|
-
spec.metadata["my_side_project_has_a_side_project"] = "https://github.com/epfl-si/rails.starterkit"
|
34
|
-
|
35
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
36
|
-
spec.files = Dir.chdir(__dir__) do
|
37
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
38
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
39
|
-
end
|
40
|
-
end
|
41
|
-
spec.require_paths = ["lib"]
|
42
|
-
|
43
|
-
spec.add_dependency "warden", "~> 1.2.0"
|
44
|
-
spec.add_dependency "dry-configurable", "~> 0.15.0"
|
45
|
-
spec.add_dependency "net-http", "~> 0.2.2"
|
46
|
-
spec.add_dependency "jwt", "~> 2.5"
|
47
|
-
end
|