grape-jwt-authentication 1.0.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6a78c43fa11b24e40395c34bbc802f61c6e4646c
4
- data.tar.gz: ca386d7ac9298045d93e38b0c6df2c3e8ab1dcae
2
+ SHA256:
3
+ metadata.gz: 00a891189e6112551bcafe2469783cbb39c6fdd1f923fd0131a8e6be9d693405
4
+ data.tar.gz: 24b25986aa9a88e9f3f279554233c5e5e2e5a3de99abb5bc4525fa31eb097429
5
5
  SHA512:
6
- metadata.gz: 1b234415911e6f3f2820d9511af016e290ed4723521374913d9cb5b5e05925722a9222f48016c7f4a75f88ee30aa901aaa702f8699e86792e4ed9d58fab439c6
7
- data.tar.gz: 1557a1c9a4fc4acb8cf77be68fbaf4f5417753bc5e859917508bd334e36ca9449e9c483119b009158f9d0ac4202413a372ed2c4638d45444e0e9d6ce9594139a
6
+ metadata.gz: 17d674a6dd8fd413e5a565d12998b3d161a0dea5fecad9b9cb16532e35634750df7049a8ceed133726f351a0f4b084403e93866ef0d546ecd67bc738021a4554
7
+ data.tar.gz: 23ccc11a897827a771c50470d61142b46eb11b3ed90f0e7cb2a3bfba7021f456e8cb1317d1eca4ac2a6c283de02b956e3aafd679cfabbaad1adcebe606546794
@@ -0,0 +1,30 @@
1
+ # http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ [*.md]
13
+ trim_trailing_whitespace = true
14
+
15
+ [*.json]
16
+ indent_style = space
17
+ indent_size = 2
18
+
19
+ [*.yml]
20
+ indent_style = space
21
+ indent_size = 2
22
+
23
+ [Makefile]
24
+ trim_trailing_whitespace = true
25
+ indent_style = tab
26
+ indent_size = 4
27
+
28
+ [*.sh]
29
+ indent_style = space
30
+ indent_size = 2
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /Gemfile.lock
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
@@ -0,0 +1,3 @@
1
+ SimpleCov.start 'test_frameworks' do
2
+ add_filter '/vendor/bundle/'
3
+ end
@@ -1,12 +1,12 @@
1
1
  env:
2
2
  global:
3
- - CC_TEST_REPORTER_ID=6e5d2de1ccc0412cdc8d01fdddc560fe3051b14f1c15fea55b0d6e32e27a81cf
3
+ - CC_TEST_REPORTER_ID=ecb753423174dbd8e4aaf04fb62bf4ef9c2a54904ac49a33fdf2b908b3c5e5f3
4
4
 
5
5
  sudo: false
6
6
  language: ruby
7
7
  rvm:
8
- - 2.2
9
- - 2.3
8
+ - 2.6
9
+ - 2.5
10
10
  - 2.4
11
11
 
12
12
  before_install: gem install bundler
@@ -15,6 +15,6 @@ before_script:
15
15
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
16
16
  - chmod +x ./cc-test-reporter
17
17
  - ./cc-test-reporter before-build
18
-
18
+ script: bundle exec rake
19
19
  after_script:
20
20
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
@@ -1,3 +1,35 @@
1
+ ### 2.0.1
2
+
3
+ * Corrected a migration bug on the configuration which used the wrong namespace
4
+ for `RsaPublicKey` (resulted in `uninitialized constant
5
+ Grape::Jwt::Authentication::Configuration::RsaPublicKey`)
6
+
7
+ ### 2.0.0
8
+
9
+ * Extracted the JWT verification functionality into its own gem
10
+ ([keyless](https://github.com/hausgold/keyless)) (#6)
11
+ * This extraction allows users to use the JWT/RSA key handling without Grape
12
+ * The API/configuration stays the same
13
+ * With the major update to 2.0 we dropped a lot of code which is now located at
14
+ the Keyless gem:
15
+ * `Grape::Jwt::Authentication::Jwt` was replace with `Keyless::Jwt`
16
+ * `Grape::Jwt::Authentication::RsaPublicKey` was replace with `Keyless::RsaPublicKey`
17
+
18
+ ### 1.3.0
19
+
20
+ * Dropped support for EOL Ruby 2.3 (in addition to Grape)
21
+
22
+ ### 1.2.0
23
+
24
+ * Check the remote response on public key fetching (#3)
25
+ * Added support for Ruby 2.6
26
+ * Dropped support for EOL Ruby 2.2
27
+
28
+ ### 1.1.0
29
+
30
+ * Added the parsed and original token to the Rack environment (#2)
31
+ * Two new helper methods are now available to access the JWT from an API spec
32
+
1
33
  ### 1.0.1
2
34
 
3
35
  * First public release of the gem
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
- ![grape-jwt-authentication](doc/assets/project.png)
1
+ ![grape-jwt-authentication](doc/assets/project.svg)
2
2
 
3
- [![Build Status](https://api.travis-ci.org/hausgold/grape-jwt-authentication.svg)](https://travis-ci.org/hausgold/grape-jwt-authentication)
3
+ [![Build Status](https://travis-ci.com/hausgold/grape-jwt-authentication.svg?branch=master)](https://travis-ci.com/hausgold/grape-jwt-authentication)
4
4
  [![Gem Version](https://badge.fury.io/rb/grape-jwt-authentication.svg)](https://badge.fury.io/rb/grape-jwt-authentication)
5
- [![Maintainability](https://api.codeclimate.com/v1/badges/446f3eff18bebff9c174/maintainability)](https://codeclimate.com/github/hausgold/grape-jwt-authentication/maintainability)
6
- [![Test Coverage](https://codeclimate.com/github/hausgold/grape-jwt-authentication/badges/coverage.svg)](https://codeclimate.com/github/hausgold/grape-jwt-authentication/coverage)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ab14a3bab572edfbf305/maintainability)](https://codeclimate.com/repos/5cac8bc06c282f791c009a66/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/ab14a3bab572edfbf305/test_coverage)](https://codeclimate.com/repos/5cac8bc06c282f791c009a66/test_coverage)
7
+ [![API docs](https://img.shields.io/badge/docs-API-blue.svg)](https://www.rubydoc.info/gems/grape-jwt-authentication)
7
8
 
8
9
  This gem is dedicated to easily integrate a JWT authentication to your
9
10
  [Grape](https://github.com/ruby-grape/grape) API. The real authentication
@@ -13,6 +14,7 @@ flexible on the JWT verification level.
13
14
  - [Installation](#installation)
14
15
  - [Usage](#usage)
15
16
  - [Grape API](#grape-api)
17
+ - [Helpers](#helpers)
16
18
  - [Configuration](#configuration)
17
19
  - [Authenticator](#authenticator)
18
20
  - [Malformed token handling](#malformed-token-handling)
@@ -72,6 +74,42 @@ module UserApi
72
74
  end
73
75
  ```
74
76
 
77
+ #### Helpers
78
+
79
+ The inclusion of the `Grape::Jwt::Authentication` inserts some helpers to
80
+ access the parsed and original JWT. This can be handy when you need to work
81
+ with the JWT payload or perform some extra calculations with the expiration
82
+ date of it. The following example demonstrated the usage of the helpers.
83
+
84
+ ```ruby
85
+ module UserApi
86
+ class ApiV1 < Grape::API
87
+ # All your fancy Grape API stuff [..]
88
+ version 'v1', using: :path
89
+
90
+ resource :payload do
91
+ desc 'A JWT payload echo service.'
92
+ get do
93
+ # The parsed JWT which has an accessible payload (RecursiveOpenStruct)
94
+ { payload: request_jwt.payload.to_h }
95
+ end
96
+ end
97
+
98
+ resource :token do
99
+ desc 'A JWT echo service.'
100
+ get do
101
+ # The original JWT parsed from the HTTP authorization header
102
+ { token: original_request_jwt }
103
+ end
104
+ end
105
+
106
+ # Enable JWT authentication on this API
107
+ include Grape::Jwt::Authentication
108
+ auth :jwt
109
+ end
110
+ end
111
+ ```
112
+
75
113
  ### Configuration
76
114
 
77
115
  This gem is quite customizable and flexible to fulfill your needs. You can make
@@ -147,18 +185,18 @@ have your own mechanism.
147
185
 
148
186
  ```ruby
149
187
  # Get your public key, by using the global configuration
150
- public_key = Grape::Jwt::Authentication::RsaPublicKey.fetch
188
+ public_key = Keyless::RsaPublicKey.fetch
151
189
  # => OpenSSL::PKey::RSA
152
190
 
153
191
  # Using a local configuration
154
- fetcher = Grape::Jwt::Authentication::RsaPublicKey.instance
192
+ fetcher = Keyless::RsaPublicKey.instance
155
193
  fetcher.url = 'https://your.identity.provider/rsa_public_key'
156
194
  public_key = fetcher.fetch
157
195
  # => OpenSSL::PKey::RSA
158
196
  ```
159
197
 
160
198
  The following examples show you how to configure the
161
- `Grape::Jwt::Authentication::RsaPublicKey` class the global way. This is useful
199
+ `Keyless::RsaPublicKey` class the global way. This is useful
162
200
  for a shared initializer place.
163
201
 
164
202
  ##### RSA public key location (URL)
@@ -223,7 +261,7 @@ the `RsaPublicKey` fetcher class. (by default)
223
261
  raw_token = 'eyJ0eXAiOiJKV1QifQ.eyJ0ZXN0Ijp0cnVlfQ.'
224
262
 
225
263
  # Parse the raw token and create a instance of it
226
- token = Grape::Jwt::Authentication::Jwt.new(raw_token)
264
+ token = Keyless::Jwt.new(raw_token)
227
265
 
228
266
  # Access the payload easily (recursive-open-struct)
229
267
  token.payload.test
@@ -236,7 +274,7 @@ token.valid?
236
274
  ```
237
275
 
238
276
  The following examples show you how to configure the
239
- `Grape::Jwt::Authentication::Jwt` class the global way. This is useful for a
277
+ `Keyless::Jwt` class the global way. This is useful for a
240
278
  shared initializer place.
241
279
 
242
280
  ##### Issuer verification
@@ -285,7 +323,7 @@ end
285
323
  You can configure your own verification key on the `Jwt` wrapper class. This
286
324
  way you can pass your HMAC secret or your ECDSA public key to the JSON Web
287
325
  Token validation method. Here you need to pass a proc, on the
288
- `Grape::Jwt::Authentication::Jwt` class it has to be a scalar value. By default
326
+ `Keyless::Jwt` class it has to be a scalar value. By default
289
327
  we use the `RsaPublicKey` class to retrieve the RSA public key.
290
328
 
291
329
  ```ruby
@@ -356,7 +394,7 @@ Grape::Jwt::Authentication.configure do |conf|
356
394
  # Custom verification logic.
357
395
  conf.authenticator = proc do |token|
358
396
  # Parse and instantiate a JWT verification instance
359
- jwt = Grape::Jwt::Authentication::Jwt.new(token)
397
+ jwt = Keyless::Jwt.new(token)
360
398
 
361
399
  # We just allow valid access tokens
362
400
  jwt.access_token? && jwt.valid?
@@ -376,8 +414,8 @@ end
376
414
  ## Development
377
415
 
378
416
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
379
- `rake spec` to run the tests. You can also run `bin/console` for an interactive
380
- prompt that will allow you to experiment.
417
+ `bundle exec rake spec` to run the tests. You can also run `bin/console` for an
418
+ interactive prompt that will allow you to experiment.
381
419
 
382
420
  To install this gem onto your local machine, run `bundle exec rake install`. To
383
421
  release a new version, update the version number in `version.rb`, and then run
@@ -0,0 +1,68 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xmlns:cc="http://creativecommons.org/ns#"
5
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+ xmlns:svg="http://www.w3.org/2000/svg"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ version="1.1"
9
+ id="Ebene_1"
10
+ x="0px"
11
+ y="0px"
12
+ viewBox="0 0 800 200"
13
+ xml:space="preserve"
14
+ width="800"
15
+ height="200"><metadata
16
+ id="metadata33"><rdf:RDF><cc:Work
17
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
18
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
19
+ id="defs31" />
20
+ <style
21
+ type="text/css"
22
+ id="style2">
23
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
24
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
25
+ .st2{fill:#132E48;}
26
+ .st3{font-family:'OpenSans-Bold';}
27
+ .st4{font-size:29.5168px;}
28
+ .st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
29
+ .st6{opacity:0.5;fill:#132E48;}
30
+ .st7{font-family:'OpenSans';}
31
+ .st8{font-size:12px;}
32
+ </style>
33
+ <g
34
+ transform="translate(0,1.53584)"
35
+ id="g828"><g
36
+ transform="translate(35.93985,35.66416)"
37
+ id="g8">
38
+ <path
39
+ style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
40
+ id="path4"
41
+ d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
42
+ class="st0" />
43
+ <path
44
+ style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
45
+ id="path6"
46
+ d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
47
+ class="st1" />
48
+ </g><text
49
+ y="106.40316"
50
+ x="192.43155"
51
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
52
+ id="text10"
53
+ class="st2 st3 st4">grape-jwt-authentication</text>
54
+ <rect
55
+ style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
56
+ id="rect12"
57
+ height="24"
58
+ width="314.5"
59
+ class="st5"
60
+ y="118.06416"
61
+ x="194.23985" /><text
62
+ y="127.22146"
63
+ x="194.21715"
64
+ style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
65
+ id="text14"
66
+ class="st6 st7 st8">A reusable Grape JWT authentication concern</text>
67
+ </g>
68
+ </svg>
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'grape/jwt/authentication/version'
6
6
 
@@ -21,19 +21,20 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ['lib']
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.16'
24
+ spec.add_development_dependency 'bundler', '>= 1.16', '< 3'
25
+ spec.add_development_dependency 'rack', '~> 2.0'
26
+ spec.add_development_dependency 'rack-test', '~> 0.8.2'
25
27
  spec.add_development_dependency 'rake', '~> 10.0'
26
28
  spec.add_development_dependency 'rspec', '~> 3.0'
27
29
  spec.add_development_dependency 'simplecov', '~> 0.15'
30
+ spec.add_development_dependency 'timecop', '~> 0.9.1'
28
31
  spec.add_development_dependency 'vcr', '~> 3.0'
29
32
  spec.add_development_dependency 'webmock', '~> 3.1'
30
- spec.add_development_dependency 'timecop', '~> 0.9.1'
31
- spec.add_development_dependency 'rack', '~> 2.0'
32
- spec.add_development_dependency 'rack-test', '~> 0.8.2'
33
33
 
34
34
  spec.add_runtime_dependency 'activesupport', '>= 3.2.0'
35
35
  spec.add_runtime_dependency 'grape', '~> 1.0'
36
36
  spec.add_runtime_dependency 'httparty'
37
37
  spec.add_runtime_dependency 'jwt', '~> 2.1'
38
38
  spec.add_runtime_dependency 'recursive-open-struct', '~> 1.0'
39
+ spec.add_runtime_dependency 'keyless', '~> 1.0'
39
40
  end
@@ -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
@@ -58,6 +58,27 @@ module Grape
58
58
  Grape::Middleware::Auth::Strategies.add(:jwt,
59
59
  JwtHandler,
60
60
  ->(opts) { [opts] })
61
+
62
+ helpers do
63
+ # Get the parsed JWT from the authorization header of the current
64
+ # request. You could use it to access the payload or the expiration
65
+ # date, etc inside your API definition. When the authenticator stated
66
+ # that the validation failed, then the parsed token is +nil+.
67
+ #
68
+ # @return [Grape::Jwt::Authentication::Jwt, nil] the parsed token
69
+ def request_jwt
70
+ env['grape_jwt_auth.parsed_token']
71
+ end
72
+
73
+ # Get the original JWT from the authorization header of the current
74
+ # request, without further changes. You could use it to display a
75
+ # custom error or to parse it differently.
76
+ #
77
+ # @return [String] the JWT from the authorization header
78
+ def original_request_jwt
79
+ env['grape_jwt_auth.original_token']
80
+ end
81
+ end
61
82
  end
62
83
  end
63
84
  end
@@ -118,11 +118,10 @@ module Grape
118
118
  # This way you can pass your HMAC secret or your ECDSA public key to
119
119
  # the JSON Web Token validation method. Here you need to pass a proc,
120
120
  # on the {Grape::Jwt::Authentication::Jwt} class it has to be a scalar
121
- # value. By default we use the
122
- # {Grape::Jwt::Authentication::RsaPublicKey} class to retrieve the RSA
123
- # public key.
121
+ # value. By default we use the {Keyless::RsaPublicKey} class to
122
+ # retrieve the RSA public key.
124
123
  config_accessor(:jwt_verification_key) do
125
- proc { RsaPublicKey.instance.fetch }
124
+ proc { Keyless::RsaPublicKey.instance.fetch }
126
125
  end
127
126
  end
128
127
  end
@@ -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
@@ -15,8 +15,12 @@ module Grape
15
15
  # A internal exception handling for malformed headers.
16
16
  class MalformedHeaderError < StandardError; end
17
17
 
18
+ # A generic JWT part, the full token contains three parts
19
+ # separated by a period.
20
+ JWT_PART_REGEX = /([a-zA-Z0-9\-_]+)?/.freeze
21
+
18
22
  # A common JWT validation regex which meets the RFC specs.
19
- JWT_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/
23
+ JWT_REGEX = Regexp.new("^#{([JWT_PART_REGEX] * 3).join('\.')}$").freeze
20
24
 
21
25
  # Initialize a new Rack middleware for Bearer token
22
26
  # processing.
@@ -76,12 +80,30 @@ module Grape
76
80
  def parse_token(header)
77
81
  token = header.to_s.scan(/^Bearer (.*)/).flatten.first
78
82
  raise MalformedHeaderError unless JWT_REGEX =~ token
83
+
79
84
  token
80
85
  end
81
86
 
87
+ # Inject the token to the environment as a parsed version. This allows
88
+ # further usage like extracting the subject from the payload when the
89
+ # verification was valid.
90
+ #
91
+ # @param env [Hash{String => Mixed}] the Rack environment
92
+ # @param token [String] the token parsed from the HTTP header
93
+ def inject_token_into_env(env, token)
94
+ env['grape_jwt_auth.parsed_token'] = Keyless::Jwt.new(token)
95
+ rescue *Keyless::Jwt::RESCUE_JWT_EXCEPTIONS
96
+ env['grape_jwt_auth.parsed_token'] = nil
97
+ ensure
98
+ env['grape_jwt_auth.original_token'] = token
99
+ end
100
+
82
101
  # Perform the authentication logic on the Rack compatible
83
102
  # interface.
84
103
  #
104
+ # @param env [Hash{String => Mixed}] the Rack environment
105
+ #
106
+ # rubocop:disable Metrics/AbcSize because thats the auth handling core
85
107
  # :reek:TooManyStatements because reek counts exception
86
108
  # handling as statements
87
109
  def call(env)
@@ -94,11 +116,16 @@ module Grape
94
116
  # errors, that why we invoke the formatter middleware here.
95
117
  Grape::Middleware::Formatter.new(->(_) {}).call(env)
96
118
 
97
- # Parse the JWT token and give it to the user defined block
119
+ # Parse the JWT token from the request headers.
120
+ token = parse_token(env['HTTP_AUTHORIZATION'])
121
+
122
+ # Inject the parsed token to the Rack environment.
123
+ inject_token_into_env(env, token)
124
+
125
+ # Give the parsed token to the user defined block
98
126
  # for futher verification. The user given block MUST return
99
127
  # a positive result to allow the request to be further
100
128
  # processed, or a negative result to stop processing.
101
- token = parse_token(env['HTTP_AUTHORIZATION'])
102
129
  raise AuthenticationError unless authenticator.call(token)
103
130
 
104
131
  # Looks like we are on a good path and the given token was
@@ -112,6 +139,7 @@ module Grape
112
139
  # Call the user defined failed authentication handler.
113
140
  failed_handler.call(token, @app)
114
141
  end
142
+ # rubocop:enable Metrics/AbcSize
115
143
  end
116
144
  end
117
145
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Jwt
5
5
  module Authentication
6
- VERSION = '1.0.1'
6
+ VERSION = '2.0.1'.freeze
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,73 +1,79 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-jwt-authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.1
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: 2017-11-23 00:00:00.000000000 Z
11
+ date: 2020-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.16'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
20
23
  type: :development
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '1.16'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
27
33
  - !ruby/object:Gem::Dependency
28
- name: rake
34
+ name: rack
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '10.0'
39
+ version: '2.0'
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
44
  - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '10.0'
46
+ version: '2.0'
41
47
  - !ruby/object:Gem::Dependency
42
- name: rspec
48
+ name: rack-test
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
51
  - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: '3.0'
53
+ version: 0.8.2
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
58
  - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: '3.0'
60
+ version: 0.8.2
55
61
  - !ruby/object:Gem::Dependency
56
- name: simplecov
62
+ name: rake
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
65
  - - "~>"
60
66
  - !ruby/object:Gem::Version
61
- version: '0.15'
67
+ version: '10.0'
62
68
  type: :development
63
69
  prerelease: false
64
70
  version_requirements: !ruby/object:Gem::Requirement
65
71
  requirements:
66
72
  - - "~>"
67
73
  - !ruby/object:Gem::Version
68
- version: '0.15'
74
+ version: '10.0'
69
75
  - !ruby/object:Gem::Dependency
70
- name: vcr
76
+ name: rspec
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
79
  - - "~>"
@@ -81,19 +87,19 @@ dependencies:
81
87
  - !ruby/object:Gem::Version
82
88
  version: '3.0'
83
89
  - !ruby/object:Gem::Dependency
84
- name: webmock
90
+ name: simplecov
85
91
  requirement: !ruby/object:Gem::Requirement
86
92
  requirements:
87
93
  - - "~>"
88
94
  - !ruby/object:Gem::Version
89
- version: '3.1'
95
+ version: '0.15'
90
96
  type: :development
91
97
  prerelease: false
92
98
  version_requirements: !ruby/object:Gem::Requirement
93
99
  requirements:
94
100
  - - "~>"
95
101
  - !ruby/object:Gem::Version
96
- version: '3.1'
102
+ version: '0.15'
97
103
  - !ruby/object:Gem::Dependency
98
104
  name: timecop
99
105
  requirement: !ruby/object:Gem::Requirement
@@ -109,33 +115,33 @@ dependencies:
109
115
  - !ruby/object:Gem::Version
110
116
  version: 0.9.1
111
117
  - !ruby/object:Gem::Dependency
112
- name: rack
118
+ name: vcr
113
119
  requirement: !ruby/object:Gem::Requirement
114
120
  requirements:
115
121
  - - "~>"
116
122
  - !ruby/object:Gem::Version
117
- version: '2.0'
123
+ version: '3.0'
118
124
  type: :development
119
125
  prerelease: false
120
126
  version_requirements: !ruby/object:Gem::Requirement
121
127
  requirements:
122
128
  - - "~>"
123
129
  - !ruby/object:Gem::Version
124
- version: '2.0'
130
+ version: '3.0'
125
131
  - !ruby/object:Gem::Dependency
126
- name: rack-test
132
+ name: webmock
127
133
  requirement: !ruby/object:Gem::Requirement
128
134
  requirements:
129
135
  - - "~>"
130
136
  - !ruby/object:Gem::Version
131
- version: 0.8.2
137
+ version: '3.1'
132
138
  type: :development
133
139
  prerelease: false
134
140
  version_requirements: !ruby/object:Gem::Requirement
135
141
  requirements:
136
142
  - - "~>"
137
143
  - !ruby/object:Gem::Version
138
- version: 0.8.2
144
+ version: '3.1'
139
145
  - !ruby/object:Gem::Dependency
140
146
  name: activesupport
141
147
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +212,20 @@ dependencies:
206
212
  - - "~>"
207
213
  - !ruby/object:Gem::Version
208
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'
209
229
  description: A reusable Grape JWT authentication concern
210
230
  email:
211
231
  - hermann.mayer92@gmail.com
@@ -213,32 +233,30 @@ executables: []
213
233
  extensions: []
214
234
  extra_rdoc_files: []
215
235
  files:
236
+ - ".editorconfig"
216
237
  - ".gitignore"
217
238
  - ".rspec"
218
239
  - ".rubocop.yml"
240
+ - ".simplecov"
219
241
  - ".travis.yml"
220
242
  - CHANGELOG.md
221
243
  - Gemfile
222
- - Gemfile.lock
223
244
  - LICENSE
224
245
  - README.md
225
246
  - Rakefile
226
247
  - bin/console
227
248
  - bin/setup
228
- - doc/assets/logo.png
229
- - doc/assets/project.png
230
- - doc/assets/project.xcf
249
+ - doc/assets/project.svg
231
250
  - grape-jwt-authentication.gemspec
232
251
  - lib/grape/jwt/authentication.rb
233
252
  - lib/grape/jwt/authentication/configuration.rb
234
- - lib/grape/jwt/authentication/jwt.rb
253
+ - lib/grape/jwt/authentication/dependencies.rb
235
254
  - lib/grape/jwt/authentication/jwt_handler.rb
236
- - lib/grape/jwt/authentication/rsa_public_key.rb
237
255
  - lib/grape/jwt/authentication/version.rb
238
256
  homepage: https://github.com/hausgold/grape-jwt-authentication
239
257
  licenses: []
240
258
  metadata: {}
241
- post_install_message:
259
+ post_install_message:
242
260
  rdoc_options: []
243
261
  require_paths:
244
262
  - lib
@@ -253,9 +271,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
271
  - !ruby/object:Gem::Version
254
272
  version: '0'
255
273
  requirements: []
256
- rubyforge_project:
257
- rubygems_version: 2.6.13
258
- signing_key:
274
+ rubygems_version: 3.1.4
275
+ signing_key:
259
276
  specification_version: 4
260
277
  summary: A reusable Grape JWT authentication concern
261
278
  test_files: []
@@ -1,114 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- grape-jwt-authentication (1.0.1)
5
- activesupport (>= 3.2.0)
6
- grape (~> 1.0)
7
- httparty
8
- jwt (~> 2.1)
9
- recursive-open-struct (~> 1.0)
10
-
11
- GEM
12
- remote: https://rubygems.org/
13
- specs:
14
- activesupport (5.1.4)
15
- concurrent-ruby (~> 1.0, >= 1.0.2)
16
- i18n (~> 0.7)
17
- minitest (~> 5.1)
18
- tzinfo (~> 1.1)
19
- addressable (2.5.2)
20
- public_suffix (>= 2.0.2, < 4.0)
21
- axiom-types (0.1.1)
22
- descendants_tracker (~> 0.0.4)
23
- ice_nine (~> 0.11.0)
24
- thread_safe (~> 0.3, >= 0.3.1)
25
- builder (3.2.3)
26
- coercible (1.0.0)
27
- descendants_tracker (~> 0.0.1)
28
- concurrent-ruby (1.0.5)
29
- crack (0.4.3)
30
- safe_yaml (~> 1.0.0)
31
- descendants_tracker (0.0.4)
32
- thread_safe (~> 0.3, >= 0.3.1)
33
- diff-lcs (1.3)
34
- docile (1.1.5)
35
- equalizer (0.0.11)
36
- grape (1.0.1)
37
- activesupport
38
- builder
39
- mustermann-grape (~> 1.0.0)
40
- rack (>= 1.3.0)
41
- rack-accept
42
- virtus (>= 1.0.0)
43
- hashdiff (0.3.7)
44
- httparty (0.15.6)
45
- multi_xml (>= 0.5.2)
46
- i18n (0.9.1)
47
- concurrent-ruby (~> 1.0)
48
- ice_nine (0.11.2)
49
- json (2.1.0)
50
- jwt (2.1.0)
51
- minitest (5.10.3)
52
- multi_xml (0.6.0)
53
- mustermann (1.0.1)
54
- mustermann-grape (1.0.0)
55
- mustermann (~> 1.0.0)
56
- public_suffix (3.0.1)
57
- rack (2.0.3)
58
- rack-accept (0.4.5)
59
- rack (>= 0.4)
60
- rack-test (0.8.2)
61
- rack (>= 1.0, < 3)
62
- rake (10.5.0)
63
- recursive-open-struct (1.0.5)
64
- rspec (3.7.0)
65
- rspec-core (~> 3.7.0)
66
- rspec-expectations (~> 3.7.0)
67
- rspec-mocks (~> 3.7.0)
68
- rspec-core (3.7.0)
69
- rspec-support (~> 3.7.0)
70
- rspec-expectations (3.7.0)
71
- diff-lcs (>= 1.2.0, < 2.0)
72
- rspec-support (~> 3.7.0)
73
- rspec-mocks (3.7.0)
74
- diff-lcs (>= 1.2.0, < 2.0)
75
- rspec-support (~> 3.7.0)
76
- rspec-support (3.7.0)
77
- safe_yaml (1.0.4)
78
- simplecov (0.15.1)
79
- docile (~> 1.1.0)
80
- json (>= 1.8, < 3)
81
- simplecov-html (~> 0.10.0)
82
- simplecov-html (0.10.2)
83
- thread_safe (0.3.6)
84
- timecop (0.9.1)
85
- tzinfo (1.2.4)
86
- thread_safe (~> 0.1)
87
- vcr (3.0.3)
88
- virtus (1.0.5)
89
- axiom-types (~> 0.1)
90
- coercible (~> 1.0)
91
- descendants_tracker (~> 0.0, >= 0.0.3)
92
- equalizer (~> 0.0, >= 0.0.9)
93
- webmock (3.1.1)
94
- addressable (>= 2.3.6)
95
- crack (>= 0.3.2)
96
- hashdiff
97
-
98
- PLATFORMS
99
- ruby
100
-
101
- DEPENDENCIES
102
- bundler (~> 1.16)
103
- grape-jwt-authentication!
104
- rack (~> 2.0)
105
- rack-test (~> 0.8.2)
106
- rake (~> 10.0)
107
- rspec (~> 3.0)
108
- simplecov (~> 0.15)
109
- timecop (~> 0.9.1)
110
- vcr (~> 3.0)
111
- webmock (~> 3.1)
112
-
113
- BUNDLED WITH
114
- 1.16.0
Binary file
Binary file
Binary file
@@ -1,112 +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
- Time.zone.at(exp)
67
- end
68
-
69
- # Deliver the public key for verification by default. This uses the
70
- # {RsaPublicKey} class, but you can configure the verification key the
71
- # way you like. (Especially for different algorithms, like HMAC or
72
- # ECDSA) Just make use of the same named setter.
73
- #
74
- # @return [OpenSSL::PKey::RSA|Mixed] The verification key
75
- def verification_key
76
- unless @verification_key
77
- conf = Grape::Jwt::Authentication.configuration
78
- return conf.jwt_verification_key.call
79
- end
80
- @verification_key
81
- end
82
-
83
- # This getter passes back the default JWT verification option hash
84
- # which is optinionated. You can change this the way you like by
85
- # configuring your options with the help of the same named setter.
86
- #
87
- # @return [Hash] The JWT verification options hash
88
- def jwt_options
89
- unless @jwt_options
90
- conf = Grape::Jwt::Authentication.configuration
91
- return conf.jwt_options.call
92
- end
93
- @jwt_options
94
- end
95
-
96
- # Verify the current token by our hard and strict rules. Whenever the
97
- # token was not parsed from a string, we encode the current state to a
98
- # JWT string representation and check this.
99
- #
100
- # @return [Boolean] Whenever the token is valid or not
101
- #
102
- # :reek:NilCheck because we have to check the token
103
- # origin and react on it
104
- def valid?
105
- JWT.decode(token, verification_key, true, jwt_options) && true
106
- rescue *RESCUE_JWT_EXCEPTIONS
107
- false
108
- end
109
- end
110
- end
111
- end
112
- end
@@ -1,126 +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
- include Singleton
16
-
17
- # Setup all the getters and setters.
18
- attr_accessor :cache
19
- attr_writer :url, :expiration, :caching
20
-
21
- # Setup the instance.
22
- def initialize
23
- @expiration = 1.hour
24
- @cache = ActiveSupport::Cache::MemoryStore.new
25
- end
26
-
27
- # Just a simple shortcut class method to access the fetch method
28
- # without specifying the singleton instance.
29
- #
30
- # @return [OpenSSL::PKey::RSA]
31
- def self.fetch
32
- instance.fetch
33
- end
34
-
35
- # Configure the single instance. This is just a wrapper (like tap)
36
- # to the instance itself.
37
- def configure
38
- yield(self)
39
- end
40
-
41
- # Fetch the public key with the help of the configuration. You can
42
- # configure the public key location (local file, remote (HTTP/HTTPS)
43
- # file), whenever we should cache and how long to cache.
44
- #
45
- # @return [OpenSSL::PKey::RSA]
46
- def fetch
47
- encoded_key = if cache?
48
- cache.fetch('encoded_key', expires_in: expiration) do
49
- fetch_encoded_key
50
- end
51
- else
52
- fetch_encoded_key
53
- end
54
-
55
- OpenSSL::PKey::RSA.new(encoded_key)
56
- end
57
-
58
- # Fetch the encoded (DER, or PEM) public key from a remote or local
59
- # location.
60
- #
61
- # @return [String] The encoded public key
62
- def fetch_encoded_key
63
- raise ArgumentError, 'No URL for RsaPublicKey configured' unless url
64
- if remote?
65
- HTTParty.get(url).body
66
- else
67
- File.read(url)
68
- end
69
- end
70
-
71
- # A helper for the caching configuration.
72
- #
73
- # @return [Boolean]
74
- def cache?
75
- caching && true
76
- end
77
-
78
- # A helper to determine if the configured URL is on a remote server or
79
- # it is local on the filesystem. Whenever the configured URL specifies
80
- # the HTTP/HTTPS protocol, we assume it is remote.
81
- #
82
- # @return [Boolean]
83
- def remote?
84
- !(url =~ /^https?/).nil?
85
- end
86
-
87
- # This getter passes back the default RSA public key. You can change
88
- # this the way you like by configuring your URL with the help of the
89
- # same named setter.
90
- #
91
- # @return [String] The configured public key location
92
- def url
93
- unless @url
94
- conf = Grape::Jwt::Authentication.configuration
95
- return conf.rsa_public_key_url
96
- end
97
- @url
98
- end
99
-
100
- # This getter passes back the default public key cache expiration time.
101
- # You can change this time with the help of the same named setter.
102
- #
103
- # @return [Integer] The configured cache expiration time
104
- def expiration
105
- unless @expiration
106
- conf = Grape::Jwt::Authentication.configuration
107
- return conf.rsa_public_key_expiration
108
- end
109
- @expiration
110
- end
111
-
112
- # This getter passes back the caching flag. You can change this flag
113
- # with the help of the same named setter.
114
- #
115
- # @return [Boolean] Whenever we should cache or not
116
- def caching
117
- unless @caching
118
- conf = Grape::Jwt::Authentication.configuration
119
- return conf.rsa_public_key_caching
120
- end
121
- @caching
122
- end
123
- end
124
- end
125
- end
126
- end