项目作者: hausgold

项目描述 :
A reusable Grape JWT authentication concern
高级语言: Ruby
项目地址: git://github.com/hausgold/grape-jwt-authentication.git
创建时间: 2017-11-20T20:49:56Z
项目社区:https://github.com/hausgold/grape-jwt-authentication

开源协议:MIT License

下载


grape-jwt-authentication

Continuous Integration
Gem Version
Test Coverage
Test Ratio
API docs

This gem is dedicated to easily integrate a JWT authentication to your
Grape API. The real authentication
functionality must be provided by the user and this makes this gem highly
flexible on the JWT verification level.

Installation

Add this line to your application’s Gemfile:

  1. gem 'grape-jwt-authentication'

And then execute:

  1. $ bundle

Or install it yourself as:

  1. $ gem install grape-jwt-authentication

Usage

Grape API

You can enable the JWT authentication on any Grape API you like. This includes
specific endpoints or a whole API. Just include the
Grape::Jwt::Authentication module and configure it the way you like.

  1. module UserApi
  2. class ApiV1 < Grape::API
  3. # All your fancy Grape API stuff [..]
  4. version 'v1', using: :path
  5. # Enable JWT authentication on this API
  6. include Grape::Jwt::Authentication
  7. auth :jwt
  8. end
  9. end

Helpers

The inclusion of the Grape::Jwt::Authentication inserts some helpers to
access the parsed and original JWT. This can be handy when you need to work
with the JWT payload or perform some extra calculations with the expiration
date of it. The following example demonstrated the usage of the helpers.

  1. module UserApi
  2. class ApiV1 < Grape::API
  3. # All your fancy Grape API stuff [..]
  4. version 'v1', using: :path
  5. resource :payload do
  6. desc 'A JWT payload echo service.'
  7. get do
  8. # The parsed JWT which has an accessible payload (RecursiveOpenStruct)
  9. { payload: request_jwt.payload.to_h }
  10. end
  11. end
  12. resource :token do
  13. desc 'A JWT echo service.'
  14. get do
  15. # The original JWT parsed from the HTTP authorization header
  16. { token: original_request_jwt }
  17. end
  18. end
  19. # Enable JWT authentication on this API
  20. include Grape::Jwt::Authentication
  21. auth :jwt
  22. end
  23. end

Configuration

This gem is quite customizable and flexible to fulfill your needs. You can make
use of some parts and leave other if you do not care about them. We are not
going to force the way how to verify JWT or work with them. Here comes a
overview of the configurations you can do.

Authenticator

The authenticator function which must be defined by the user to verify the
given JSON Web Token. Here comes all your logic to lookup the related user on
your database, the token claim verification and/or the token cryptographic
signing. The function must return true or false to indicate the validity of the
token.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.authenticator = proc do |token|
  3. # Verify the token the way you like. (true, false)
  4. end
  5. end

Malformed token handling

Whenever the given value on the Authorization header is not a valid Bearer
authentication scheme or the token itself is not a valid JSON Web Token, this
user defined function will be called. You can add custom handling of this
situations, like responding a different HTTP status code, or a more detailed
response body. By default the Rack stack will be interrupted and a response
with the 400 Bad Request status code will be send to the client. The raw
token (value of the Authorization header) and the Rack app will be injected
to your function for maximum flexibility.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.malformed_auth_handler = proc do |raw_token, app|
  3. # Do your own error handling. (Rack interface)
  4. end
  5. end

Failed authentication handling

When the client sends a correctly formatted JSON Web Token with the Bearer
authentication scheme within the Authorization header and your authenticator
fails for some reason (token claims, wrong audience, bad subject, expired
token, wrong cryptographic signing etc), this function is called to handle the
bad authentication. By default the Rack stack will be interrupted and a
response with the 401 Unauthorized status code will be send to the client.
You can customize this the way you like and send different error codes, or
handle the error completely different. The parsed JSON Web Token and the Rack
app will be injected to your function to allow any customized error handling.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.failed_auth_handler = proc do |token, app|
  3. # Do your own error handling. (Rack interface)
  4. end
  5. end

RSA public key helper

We provide a straightforward solution to deal with the provision of RSA public
keys. Sometimes you want to distribute them by file to each machine and have
a local access, and sometimes you provide an endpoint on your identity
provider to fetch the RSA public key via HTTP/HTTPS. The RsaPublicKey class
helps you to fulfill this task easily.

Heads up! You can skip this if you do not care about RSA verification or
have your own mechanism.

  1. # Get your public key, by using the global configuration
  2. public_key = Keyless::RsaPublicKey.fetch
  3. # => OpenSSL::PKey::RSA
  4. # Using a local configuration
  5. fetcher = Keyless::RsaPublicKey.instance
  6. fetcher.url = 'https://your.identity.provider/rsa_public_key'
  7. public_key = fetcher.fetch
  8. # => OpenSSL::PKey::RSA

The following examples show you how to configure the
Keyless::RsaPublicKey class the global way. This is useful
for a shared initializer place.

RSA public key location (URL)

Whenever you want to use the RsaPublicKey class you configure the default URL
on the singleton instance, or use the gem configure method and set it up
accordingly. We allow the fetch of the public key from a remote server
(HTTP/HTTPS) or from a local file which is accessible by the ruby process.
Specify the URL or the local path here. Not specified by default.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. # Local file
  3. conf.rsa_public_key_url = '/tmp/jwt_rsa.pub'
  4. # Remote URL
  5. conf.rsa_public_key_url = 'https://your.identity.provider/rsa_public_key'
  6. end
RSA public key caching

You can configure the RsaPublickey class to enable/disable caching. For a
remote public key location it is handy to cache the result for some time to
keep the traffic low to the resource server. For a local file you can skip
this. Disabled by default.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.rsa_public_key_caching = true
  3. end
RSA public key cache expiration

When you make use of the cache of the RsaPublicKey class you can fine tune
the expiration time. The RSA public key from your identity
provider should not change this frequent, so a cache for at least one hour is
fine. You should not set it lower than one minute. Keep this setting in mind
when you change keys. Your infrastructure could be inoperable for this
configured time. One hour by default.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.rsa_public_key_expiration = 1.hour
  3. end

JWT instance helper

We ship a little wrapper class to ease the validation of JSON Web Tokens with
the help of the great ruby-jwt library. This
wrapper class provides some helpers like #access_token?, #refresh_token? or
#expires_at which returns a ActiveSupport time-zoned representation of the
token expiration timestamp. It is initially opinionated to RSA verification,
but can be tuned to verify HMAC or ECDSA signed tokens. It integrated well with
the RsaPublicKey fetcher class. (by default)

Heads up! You can skip this if you have your own JWT verification mechanism.

  1. # A raw JWT (no signing, payload: {test: true})
  2. raw_token = 'eyJ0eXAiOiJKV1QifQ.eyJ0ZXN0Ijp0cnVlfQ.'
  3. # Parse the raw token and create a instance of it
  4. token = Keyless::Jwt.new(raw_token)
  5. # Access the payload easily (recursive-open-struct)
  6. token.payload.test
  7. # => true
  8. # Validate the token (we assume you configured the verification key, an/or
  9. # you own custom JWT verification options here)
  10. token.valid?
  11. # => true

The following examples show you how to configure the
Keyless::Jwt class the global way. This is useful for a
shared initializer place.

Issuer verification

The JSON Web Token issuer which should be used for verification. When nil we
also turn off the verification by default. (See the default JWT options)

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.jwt_issuer = 'your-identity-provider'
  3. end
Beholder (audience) verification

The resource server (namely the one which configures this right now)
which MUST be present on the JSON Web Token audience claim. When nil we
also turn off the verification by default. (See the default JWT options)

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.jwt_beholder = 'your-resource-server'
  3. end
Custom JWT verification options

You can configure a different JSON Web Token verification option hash if your
algorithm differs or you want some extra/different options. Just watch out
that you have to pass a proc to this configuration property. On the
Keyless::Jwt class it has to be a simple hash. The default
is here the RS256 algorithm with enabled expiration check, and issuer+audience
check when the jwt_issuer / jwt_beholder are configured accordingly.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.jwt_options = proc do
  3. # See: https://github.com/jwt/ruby-jwt
  4. { algorithm: 'HS256' }
  5. end
  6. end
Custom JWT verification key

You can configure your own verification key on the Jwt wrapper class. This
way you can pass your HMAC secret or your ECDSA public key to the JSON Web
Token validation method. Here you need to pass a proc, on the
Keyless::Jwt class it has to be a scalar value. By default
we use the RsaPublicKey class to retrieve the RSA public key.

  1. Grape::Jwt::Authentication.configure do |conf|
  2. conf.jwt_verification_key = proc do
  3. # Retrieve your verification key (RSA, ECDSA, HMAC secret)
  4. # the way you like, and pass it back here.
  5. end
  6. end

Per-API configuration

Imagine the migration of your API (say v2) and also the JSON Web Token payload
changes in a way you need to handle. Maybe you want to be more strict on
version 2 than on your old version 1. For this you can make use of the local
configuration of the JWT authenticator, on your specific Grape API declaration.
Here comes an example usage:

  1. module UserApi
  2. class ApiV2 < Grape::API
  3. v2_auth_malformed = proc { |raw_token, app| [400, {}, ['Malformed!']] }
  4. v2_auth_failed = proc { |token, app| [401, {}, ['Go away!']] }
  5. # Enable JWT authentication on this API
  6. include Grape::Jwt::Authentication
  7. auth(:jwt, malformed: v2_auth_malformed,
  8. failed: v2_auth_failed) do |token|
  9. # Your new stricter v2 authenticator.
  10. false
  11. end
  12. end
  13. end

Full RSA256 example

Here comes a full example of the opinionated RSA256 algorithm usage with a
remote RSA public key location, enabled caching and a full token payload
verification.

  1. # On an initializer ..
  2. Grape::Jwt::Authentication.configure do |conf|
  3. # The remote RSA public key location and enabled caching to limit the
  4. # traffic on the remote server.
  5. conf.rsa_public_key_url = 'https://your.identity.provider/rsa_public_key'
  6. conf.rsa_public_key_caching = true
  7. conf.rsa_public_key_expiration = 10.minutes
  8. # Configure the JWT wrapper.
  9. conf.jwt_issuer = 'The Identity Provider'
  10. conf.jwt_beholder = 'example-api'
  11. # Let Grape handle the malformed error with correct response formatting.
  12. # (XML, JSON)
  13. conf.malformed_auth_handler = proc do |raw_token, app|
  14. raise ArgumentError, 'Authorization header is malformed.'
  15. end
  16. # The same procedure for failed verifications. (XML, JSON formatting handled
  17. # external by Grape)
  18. conf.failed_auth_handler = proc do |token, app|
  19. raise ArgumentError, 'Access denied.'
  20. end
  21. # Custom verification logic.
  22. conf.authenticator = proc do |token|
  23. # Parse and instantiate a JWT verification instance
  24. jwt = Keyless::Jwt.new(token)
  25. # We just allow valid access tokens
  26. jwt.access_token? && jwt.valid?
  27. end
  28. end
  29. # On your Grape API ..
  30. module UserApi
  31. class ApiV1 < Grape::API
  32. # Enable JWT authentication on this API
  33. include Grape::Jwt::Authentication
  34. auth :jwt
  35. end
  36. end

Development

After checking out the repo, run make install to install dependencies. Then,
run make test to run the tests. You can also run make shell-irb for an
interactive prompt that will allow you to experiment.

Code of Conduct

Everyone interacting in the project codebase, issue tracker, chat
rooms and mailing lists is expected to follow the code of
conduct
.

Contributing

Bug reports and pull requests are welcome on GitHub at
https://github.com/hausgold/grape-jwt-authentication. Make sure that every pull request adds
a bullet point to the changelog file with a reference to the
actual pull request.

Releasing

The release process of this Gem is fully automated. You just need to open the
Github Actions Release
Workflow

and trigger a new run via the Run workflow button. Insert the new version
number (check the changelog first for the latest release) and
you’re done.