grape-jwt-authentication 1.3.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +7 -7
- data/grape-jwt-authentication.gemspec +1 -0
- data/lib/grape/jwt/authentication.rb +4 -4
- data/lib/grape/jwt/authentication/dependencies.rb +33 -0
- data/lib/grape/jwt/authentication/jwt_handler.rb +2 -2
- data/lib/grape/jwt/authentication/version.rb +1 -1
- metadata +21 -8
- data/lib/grape/jwt/authentication/jwt.rb +0 -113
- data/lib/grape/jwt/authentication/rsa_public_key.rb +0 -132
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30a5d4d0a29a146c657ef5452513e1ffd546fca42ee41c08ed6096537b4b454f
|
4
|
+
data.tar.gz: ab6fc80154deca0a7c61b775678aa22965283cb1f9b528cca0db97c4ebdcc301
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 460a1222afd6240182b328f4bcc69fa44a4e2a18d9c63f478121e59853da3585265b589a50ebd8bdd0d67e483b44d8abfc743cdbfff04fef9c2b00357fc759b7
|
7
|
+
data.tar.gz: a38e6870649e183a3329a8eb3b078c7254c6cb01dac1e704ef7d164d0ef8795999e7d7fe8ca6bdc3f3d192b628869c3a089702b06c6eb398d3c10acc82fffb63
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
### 2.0.0
|
2
|
+
|
3
|
+
* Extracted the JWT verification functionality into its own gem
|
4
|
+
([keyless](https://github.com/hausgold/keyless)) (#6)
|
5
|
+
* This extraction allows users to use the JWT/RSA key handling without Grape
|
6
|
+
* The API/configuration stays the same
|
7
|
+
* With the major update to 2.0 we dropped a lot of code which is now located at
|
8
|
+
the Keyless gem:
|
9
|
+
* `Grape::Jwt::Authentication::Jwt` was replace with `Keyless::Jwt`
|
10
|
+
* `Grape::Jwt::Authentication::RsaPublicKey` was replace with `Keyless::RsaPublicKey`
|
11
|
+
|
1
12
|
### 1.3.0
|
2
13
|
|
3
14
|
* Dropped support for EOL Ruby 2.3 (in addition to Grape)
|
data/README.md
CHANGED
@@ -185,18 +185,18 @@ have your own mechanism.
|
|
185
185
|
|
186
186
|
```ruby
|
187
187
|
# Get your public key, by using the global configuration
|
188
|
-
public_key =
|
188
|
+
public_key = Keyless::RsaPublicKey.fetch
|
189
189
|
# => OpenSSL::PKey::RSA
|
190
190
|
|
191
191
|
# Using a local configuration
|
192
|
-
fetcher =
|
192
|
+
fetcher = Keyless::RsaPublicKey.instance
|
193
193
|
fetcher.url = 'https://your.identity.provider/rsa_public_key'
|
194
194
|
public_key = fetcher.fetch
|
195
195
|
# => OpenSSL::PKey::RSA
|
196
196
|
```
|
197
197
|
|
198
198
|
The following examples show you how to configure the
|
199
|
-
`
|
199
|
+
`Keyless::RsaPublicKey` class the global way. This is useful
|
200
200
|
for a shared initializer place.
|
201
201
|
|
202
202
|
##### RSA public key location (URL)
|
@@ -261,7 +261,7 @@ the `RsaPublicKey` fetcher class. (by default)
|
|
261
261
|
raw_token = 'eyJ0eXAiOiJKV1QifQ.eyJ0ZXN0Ijp0cnVlfQ.'
|
262
262
|
|
263
263
|
# Parse the raw token and create a instance of it
|
264
|
-
token =
|
264
|
+
token = Keyless::Jwt.new(raw_token)
|
265
265
|
|
266
266
|
# Access the payload easily (recursive-open-struct)
|
267
267
|
token.payload.test
|
@@ -274,7 +274,7 @@ token.valid?
|
|
274
274
|
```
|
275
275
|
|
276
276
|
The following examples show you how to configure the
|
277
|
-
`
|
277
|
+
`Keyless::Jwt` class the global way. This is useful for a
|
278
278
|
shared initializer place.
|
279
279
|
|
280
280
|
##### Issuer verification
|
@@ -323,7 +323,7 @@ end
|
|
323
323
|
You can configure your own verification key on the `Jwt` wrapper class. This
|
324
324
|
way you can pass your HMAC secret or your ECDSA public key to the JSON Web
|
325
325
|
Token validation method. Here you need to pass a proc, on the
|
326
|
-
`
|
326
|
+
`Keyless::Jwt` class it has to be a scalar value. By default
|
327
327
|
we use the `RsaPublicKey` class to retrieve the RSA public key.
|
328
328
|
|
329
329
|
```ruby
|
@@ -394,7 +394,7 @@ Grape::Jwt::Authentication.configure do |conf|
|
|
394
394
|
# Custom verification logic.
|
395
395
|
conf.authenticator = proc do |token|
|
396
396
|
# Parse and instantiate a JWT verification instance
|
397
|
-
jwt =
|
397
|
+
jwt = Keyless::Jwt.new(token)
|
398
398
|
|
399
399
|
# We just allow valid access tokens
|
400
400
|
jwt.access_token? && jwt.valid?
|
@@ -7,15 +7,13 @@ require 'active_support/cache'
|
|
7
7
|
require 'active_support/core_ext/hash'
|
8
8
|
require 'active_support/time'
|
9
9
|
require 'active_support/time_with_zone'
|
10
|
-
|
11
10
|
require 'jwt'
|
12
|
-
|
11
|
+
require 'keyless'
|
13
12
|
require 'grape'
|
14
13
|
require 'grape/jwt/authentication/version'
|
15
14
|
require 'grape/jwt/authentication/configuration'
|
15
|
+
require 'grape/jwt/authentication/dependencies'
|
16
16
|
require 'grape/jwt/authentication/jwt_handler'
|
17
|
-
require 'grape/jwt/authentication/jwt'
|
18
|
-
require 'grape/jwt/authentication/rsa_public_key'
|
19
17
|
|
20
18
|
module Grape
|
21
19
|
module Jwt
|
@@ -43,11 +41,13 @@ module Grape
|
|
43
41
|
# end
|
44
42
|
def self.configure
|
45
43
|
yield(configuration)
|
44
|
+
configure_dependencies
|
46
45
|
end
|
47
46
|
|
48
47
|
# Reset the current configuration with the default one.
|
49
48
|
def self.reset_configuration!
|
50
49
|
self.configuration = Configuration.new
|
50
|
+
configure_dependencies
|
51
51
|
end
|
52
52
|
|
53
53
|
included do
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Jwt
|
5
|
+
module Authentication
|
6
|
+
# Specifies which configuration keys are shared between keyless
|
7
|
+
# and grape-jwt-authentication, so that we can easily pass through
|
8
|
+
# our configuration to keyless.
|
9
|
+
KEYLESS_CONFIGURATION = %i[
|
10
|
+
authenticator rsa_public_key_url rsa_public_key_caching
|
11
|
+
rsa_public_key_expiration jwt_issuer jwt_beholder jwt_options
|
12
|
+
jwt_verification_key
|
13
|
+
]
|
14
|
+
|
15
|
+
# (Re)configure our gem dependencies. We take care of setting up
|
16
|
+
# +Keyless+, which has been extracted from this gem.
|
17
|
+
def self.configure_dependencies
|
18
|
+
configure_keyless
|
19
|
+
end
|
20
|
+
|
21
|
+
# Configure the +Keyless+ gem with our configuration.
|
22
|
+
def self.configure_keyless
|
23
|
+
configuration = Grape::Jwt::Authentication.configuration
|
24
|
+
|
25
|
+
Keyless.configure do |keyless|
|
26
|
+
KEYLESS_CONFIGURATION.each do |option|
|
27
|
+
keyless.send("#{option}=", configuration.send(option))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -91,8 +91,8 @@ module Grape
|
|
91
91
|
# @param env [Hash{String => Mixed}] the Rack environment
|
92
92
|
# @param token [String] the token parsed from the HTTP header
|
93
93
|
def inject_token_into_env(env, token)
|
94
|
-
env['grape_jwt_auth.parsed_token'] = Jwt.new(token)
|
95
|
-
rescue *Jwt::RESCUE_JWT_EXCEPTIONS
|
94
|
+
env['grape_jwt_auth.parsed_token'] = Keyless::Jwt.new(token)
|
95
|
+
rescue *Keyless::Jwt::RESCUE_JWT_EXCEPTIONS
|
96
96
|
env['grape_jwt_auth.parsed_token'] = nil
|
97
97
|
ensure
|
98
98
|
env['grape_jwt_auth.original_token'] = token
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-jwt-authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hermann Mayer
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -212,6 +212,20 @@ dependencies:
|
|
212
212
|
- - "~>"
|
213
213
|
- !ruby/object:Gem::Version
|
214
214
|
version: '1.0'
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: keyless
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '1.0'
|
222
|
+
type: :runtime
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '1.0'
|
215
229
|
description: A reusable Grape JWT authentication concern
|
216
230
|
email:
|
217
231
|
- hermann.mayer92@gmail.com
|
@@ -236,14 +250,13 @@ files:
|
|
236
250
|
- grape-jwt-authentication.gemspec
|
237
251
|
- lib/grape/jwt/authentication.rb
|
238
252
|
- lib/grape/jwt/authentication/configuration.rb
|
239
|
-
- lib/grape/jwt/authentication/
|
253
|
+
- lib/grape/jwt/authentication/dependencies.rb
|
240
254
|
- lib/grape/jwt/authentication/jwt_handler.rb
|
241
|
-
- lib/grape/jwt/authentication/rsa_public_key.rb
|
242
255
|
- lib/grape/jwt/authentication/version.rb
|
243
256
|
homepage: https://github.com/hausgold/grape-jwt-authentication
|
244
257
|
licenses: []
|
245
258
|
metadata: {}
|
246
|
-
post_install_message:
|
259
|
+
post_install_message:
|
247
260
|
rdoc_options: []
|
248
261
|
require_paths:
|
249
262
|
- lib
|
@@ -258,8 +271,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
258
271
|
- !ruby/object:Gem::Version
|
259
272
|
version: '0'
|
260
273
|
requirements: []
|
261
|
-
rubygems_version: 3.1.
|
262
|
-
signing_key:
|
274
|
+
rubygems_version: 3.1.4
|
275
|
+
signing_key:
|
263
276
|
specification_version: 4
|
264
277
|
summary: A reusable Grape JWT authentication concern
|
265
278
|
test_files: []
|
@@ -1,113 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'recursive-open-struct'
|
4
|
-
|
5
|
-
module Grape
|
6
|
-
module Jwt
|
7
|
-
module Authentication
|
8
|
-
# A easy to use model for verification of JSON Web Tokens. This is just a
|
9
|
-
# wrapper class for the excellent ruby-jwt gem. It's completely up to you
|
10
|
-
# to use it. But be aware, its a bit optinionated by default.
|
11
|
-
class Jwt
|
12
|
-
# All the following JWT verification issues lead to a failed validation.
|
13
|
-
RESCUE_JWT_EXCEPTIONS = [
|
14
|
-
::JWT::DecodeError,
|
15
|
-
::JWT::VerificationError,
|
16
|
-
::JWT::ExpiredSignature,
|
17
|
-
::JWT::IncorrectAlgorithm,
|
18
|
-
::JWT::ImmatureSignature,
|
19
|
-
::JWT::InvalidIssuerError,
|
20
|
-
::JWT::InvalidIatError,
|
21
|
-
::JWT::InvalidAudError,
|
22
|
-
::JWT::InvalidSubError,
|
23
|
-
::JWT::InvalidJtiError,
|
24
|
-
::JWT::InvalidPayload
|
25
|
-
].freeze
|
26
|
-
|
27
|
-
# :reek:Attribute because its fine to be extern-modifiable at these
|
28
|
-
# instances
|
29
|
-
attr_reader :payload, :token
|
30
|
-
attr_writer :verification_key, :jwt_options
|
31
|
-
attr_accessor :issuer, :beholder
|
32
|
-
|
33
|
-
# Setup a new JWT instance. You have to pass the raw JSON Web Token to
|
34
|
-
# the initializer. Example:
|
35
|
-
#
|
36
|
-
# Jwt.new('j.w.t')
|
37
|
-
# # => <Jwt>
|
38
|
-
#
|
39
|
-
# @return [Jwt]
|
40
|
-
def initialize(token)
|
41
|
-
parsed_payload = JWT.decode(token, nil, false).first.symbolize_keys
|
42
|
-
@token = token
|
43
|
-
@payload = RecursiveOpenStruct.new(parsed_payload)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Checks if the payload says this is a refresh token.
|
47
|
-
#
|
48
|
-
# @return [Boolean] Whenever this is a access token
|
49
|
-
def access_token?
|
50
|
-
payload.typ == 'access'
|
51
|
-
end
|
52
|
-
|
53
|
-
# Checks if the payload says this is a refresh token.
|
54
|
-
#
|
55
|
-
# @return [Boolean] Whenever this is a refresh token
|
56
|
-
def refresh_token?
|
57
|
-
payload.typ == 'refresh'
|
58
|
-
end
|
59
|
-
|
60
|
-
# Retrives the expiration date from the payload when set.
|
61
|
-
#
|
62
|
-
# @return [nil|ActiveSupport::TimeWithZone] The expiration date
|
63
|
-
def expires_at
|
64
|
-
exp = payload.exp
|
65
|
-
return nil unless exp
|
66
|
-
|
67
|
-
Time.zone.at(exp)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Deliver the public key for verification by default. This uses the
|
71
|
-
# {RsaPublicKey} class, but you can configure the verification key the
|
72
|
-
# way you like. (Especially for different algorithms, like HMAC or
|
73
|
-
# ECDSA) Just make use of the same named setter.
|
74
|
-
#
|
75
|
-
# @return [OpenSSL::PKey::RSA|Mixed] The verification key
|
76
|
-
def verification_key
|
77
|
-
unless @verification_key
|
78
|
-
conf = Grape::Jwt::Authentication.configuration
|
79
|
-
return conf.jwt_verification_key.call
|
80
|
-
end
|
81
|
-
@verification_key
|
82
|
-
end
|
83
|
-
|
84
|
-
# This getter passes back the default JWT verification option hash
|
85
|
-
# which is optinionated. You can change this the way you like by
|
86
|
-
# configuring your options with the help of the same named setter.
|
87
|
-
#
|
88
|
-
# @return [Hash] The JWT verification options hash
|
89
|
-
def jwt_options
|
90
|
-
unless @jwt_options
|
91
|
-
conf = Grape::Jwt::Authentication.configuration
|
92
|
-
return conf.jwt_options.call
|
93
|
-
end
|
94
|
-
@jwt_options
|
95
|
-
end
|
96
|
-
|
97
|
-
# Verify the current token by our hard and strict rules. Whenever the
|
98
|
-
# token was not parsed from a string, we encode the current state to a
|
99
|
-
# JWT string representation and check this.
|
100
|
-
#
|
101
|
-
# @return [Boolean] Whenever the token is valid or not
|
102
|
-
#
|
103
|
-
# :reek:NilCheck because we have to check the token
|
104
|
-
# origin and react on it
|
105
|
-
def valid?
|
106
|
-
JWT.decode(token, verification_key, true, jwt_options) && true
|
107
|
-
rescue *RESCUE_JWT_EXCEPTIONS
|
108
|
-
false
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'singleton'
|
4
|
-
require 'openssl'
|
5
|
-
require 'httparty'
|
6
|
-
|
7
|
-
module Grape
|
8
|
-
module Jwt
|
9
|
-
module Authentication
|
10
|
-
# A common purpose RSA public key fetching/caching helper. With the help
|
11
|
-
# of this class you are able to retrieve the RSA public key from a remote
|
12
|
-
# server or a local file. This is naturally only useful if you care about
|
13
|
-
# JSON Web Token which are signed by the RSA algorithm.
|
14
|
-
class RsaPublicKey
|
15
|
-
# A internal exception handling for failed fetch attempts.
|
16
|
-
class FetchError < StandardError; end
|
17
|
-
|
18
|
-
include Singleton
|
19
|
-
|
20
|
-
# Setup all the getters and setters.
|
21
|
-
attr_accessor :cache
|
22
|
-
attr_writer :url, :expiration, :caching
|
23
|
-
|
24
|
-
# Setup the instance.
|
25
|
-
def initialize
|
26
|
-
@expiration = 1.hour
|
27
|
-
@cache = ActiveSupport::Cache::MemoryStore.new
|
28
|
-
end
|
29
|
-
|
30
|
-
# Just a simple shortcut class method to access the fetch method
|
31
|
-
# without specifying the singleton instance.
|
32
|
-
#
|
33
|
-
# @return [OpenSSL::PKey::RSA]
|
34
|
-
def self.fetch
|
35
|
-
instance.fetch
|
36
|
-
end
|
37
|
-
|
38
|
-
# Configure the single instance. This is just a wrapper (like tap)
|
39
|
-
# to the instance itself.
|
40
|
-
def configure
|
41
|
-
yield(self)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Fetch the public key with the help of the configuration. You can
|
45
|
-
# configure the public key location (local file, remote (HTTP/HTTPS)
|
46
|
-
# file), whenever we should cache and how long to cache.
|
47
|
-
#
|
48
|
-
# @return [OpenSSL::PKey::RSA]
|
49
|
-
def fetch
|
50
|
-
encoded_key = if cache?
|
51
|
-
cache.fetch('encoded_key', expires_in: expiration) do
|
52
|
-
fetch_encoded_key
|
53
|
-
end
|
54
|
-
else
|
55
|
-
fetch_encoded_key
|
56
|
-
end
|
57
|
-
|
58
|
-
OpenSSL::PKey::RSA.new(encoded_key)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Fetch the encoded (DER, or PEM) public key from a remote or local
|
62
|
-
# location.
|
63
|
-
#
|
64
|
-
# @return [String] The encoded public key
|
65
|
-
def fetch_encoded_key
|
66
|
-
raise ArgumentError, 'No URL for RsaPublicKey configured' unless url
|
67
|
-
|
68
|
-
if remote?
|
69
|
-
res = HTTParty.get(url)
|
70
|
-
raise FetchError, res.inspect unless (200..299).include? res.code
|
71
|
-
res.body
|
72
|
-
else
|
73
|
-
File.read(url)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# A helper for the caching configuration.
|
78
|
-
#
|
79
|
-
# @return [Boolean]
|
80
|
-
def cache?
|
81
|
-
caching && true
|
82
|
-
end
|
83
|
-
|
84
|
-
# A helper to determine if the configured URL is on a remote server or
|
85
|
-
# it is local on the filesystem. Whenever the configured URL specifies
|
86
|
-
# the HTTP/HTTPS protocol, we assume it is remote.
|
87
|
-
#
|
88
|
-
# @return [Boolean]
|
89
|
-
def remote?
|
90
|
-
!(url =~ /^https?/).nil?
|
91
|
-
end
|
92
|
-
|
93
|
-
# This getter passes back the default RSA public key. You can change
|
94
|
-
# this the way you like by configuring your URL with the help of the
|
95
|
-
# same named setter.
|
96
|
-
#
|
97
|
-
# @return [String] The configured public key location
|
98
|
-
def url
|
99
|
-
unless @url
|
100
|
-
conf = Grape::Jwt::Authentication.configuration
|
101
|
-
return conf.rsa_public_key_url
|
102
|
-
end
|
103
|
-
@url
|
104
|
-
end
|
105
|
-
|
106
|
-
# This getter passes back the default public key cache expiration time.
|
107
|
-
# You can change this time with the help of the same named setter.
|
108
|
-
#
|
109
|
-
# @return [Integer] The configured cache expiration time
|
110
|
-
def expiration
|
111
|
-
unless @expiration
|
112
|
-
conf = Grape::Jwt::Authentication.configuration
|
113
|
-
return conf.rsa_public_key_expiration
|
114
|
-
end
|
115
|
-
@expiration
|
116
|
-
end
|
117
|
-
|
118
|
-
# This getter passes back the caching flag. You can change this flag
|
119
|
-
# with the help of the same named setter.
|
120
|
-
#
|
121
|
-
# @return [Boolean] Whenever we should cache or not
|
122
|
-
def caching
|
123
|
-
unless @caching
|
124
|
-
conf = Grape::Jwt::Authentication.configuration
|
125
|
-
return conf.rsa_public_key_caching
|
126
|
-
end
|
127
|
-
@caching
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|