This gem provides a method for handling JWT authentication between AS services.
Add this line to your application's Gemfile:
# make sure gemfury source is also in this file.
gem 'action_sprout-jwt_auth'
And then execute:
$ bundle
Here is an example of how ActionSprout::JWTAuth is used in ActionSprout/fern and ActionSprout/waterleaf.
# Generate a JWT in fern
jwt_string = ActionSprout::JWTAuth.generate_jwt({ 'organization_id' => 1 }, issuer: 'as-fern', key: Fern.private_key)
# Verify that JWT in waterleaf
ActionSprout::JWTAuth.verify_jwt jwt_string, key: ferns_public_key # => true or false
ActionSprout::JWTAuth provides a handy method that can be used as a before_action
: verify_jwt!
.
To use it, specify verify_jwt!
as a before_action
. For example:
class MyController < ActionController::Base
before_action :verify_jwt!
end
By default ActionSprout::JWTAuth
can make a HTTP request to get the public key it needs to verify the incomming JWT. Three environment variables are required for this to work:
-
JWT_KEY_SERVER_URL_TEMPLATE
A url template for where to find the public key. The parameter
{issuer}
will be filled with theiss
from the incoming JWT.For example:
http://keys.actionsprout.com/api/keys/{issuer}
-
APP_NAME
Used to generate a JWT to authenticate the request for a public key.
-
PRIVATE_KEY
Used to sign a JWT to authenticate the request for a public key.
The response from the key server is expected to be a JSONAPI resource object with the attribute key
that contains the PEM representation of the public key.
Internally, ActionSprout::JWTAuth
calls #public_key_for_jwt_auth
on your controller. To prevent it from making an HTTP request to find the public key to verify an incoming JWT, define #public_key_for_jwt_auth
to return the PEM representation of the public key.
class MyController < ActionController::Base
before_action :verify_jwt!
private
def public_key_for_jwt_auth
ENV.fetch('OTHER_APP_PUBLIC_KEY')
end
end
By default, :verify_jwt!
verifies that the JWT includes the aud
claim with the request url.
To generate a JWT that passes this verification, include an 'aud'
in the payload as an array with the url of the request.
Here is an example using HTTParty.
url = 'http://example.com/api/people'
query = { name: 'Princess Buttercup' }
jwt = AsJWTAuth.generate_jwt('aud' => [url])
headers = { 'X-Auth' => jwt }
HTTParty.get url, query: query, headers: headers
The options used to verify the JWT can be customized by overwriting jwt_options_for_verification
in the controller.
Please see the jwt-ruby
documentation for examples of claims available for verification. Any claim supported by jwt-ruby
can be specified in jwt_options_for_verification
.
For example, to verify the request is issued by a specific app, try something like this:
def jwt_options_for_verification
{ verify_iss: true, iss: 'other-app' }
end
Or, to disable this type of verification entirely:
def jwt_options_for_verification
{}
end
It is not recommended to disable this verification unless some other method is used to authorize the data returned (such as using something in the token to scope the data returned).
ActionSprout::JWTAuth provides some handy test helpers. For example:
before { assume_valid_jwt from: 'as-issuer' }
# or
before { set_jwt_auth user_id: 1 }
ActionSprout::JWTAuth also provides a shared example to be used to verify that a request is protected by JWT. You must define the request.
The request can either be in a before
block, or defined as let(:request)
.
For example:
it_should_behave_like 'a JWT authorized request' do
let(:response) { get :index }
end
Or:
context 'the index page' do
let(:response) { get :index }
it_should_behave_like 'a JWT authorized request'
end
If you are seeing this error and have defined a request, it is possible that your request is raising an exception. Check rspec results for other exceptions.
Some requests may require a specific JWT sub
. You can define a custom value through jwt_sub
:
context 'the index page' do
...
it_should_behave_like 'a JWT authorized request' do
let(:jwt_sub) { ... }
end
end
Currently, each service so far already knows what other service is expected to be making the call, but in the future, it may be that an endpoint is accessible by more than one service. In this case, the service will need to determine who has made the call in order to use the correct public key to verify with.
When we have that requirement, we plan to add logic and configuration to ActionSprout::JWTAuth to handle finding the correct public key.
- Check out repo
https://github.com/ActionSprout/action_sprout-jwt_auth.git
bin/setup
(this runsbundle install
)- Run tests with
rake spec
You can also run bin/console
for an interactive prompt that will allow you to experiment.
Use locally To install this gem into a local project add to your Gemfile
gem 'action_sprout-jwt_auth', path: 'relative/path/to/gem/repo'
and then bundle install
in that project. Remember to add the Gemfury source
before
using in production.
Update the version number in lib/action_sprout/jwt_auth/version.rb
and make you final version change commit.
Create a release tag with gem_push=no rake release
.
Make sure you have the git remote fury
added (can be called anything).
$ git remote add fury https://[email protected]/ORGANIZATION/
To release the new verison we push to our Gemfury repo.
$ git push fury master