README.md

Warden::JWTAuth

This gem is just a replacement for cookies when these can't be used. As
cookies, a token expired with warden-jwt_auth will mandatorily have an
expiration time. If you need that your users never sign out, you will be better
off with a solution using refresh tokens, like some implementation of OAuth2.

You can read about which security concerns this library takes into account and about JWT generic secure usage in the following series of posts:

Secret key configuration

First of all, you have to configure the secret key that will be used to sign generated tokens.

Warden::JWTAuth.configure do |config|
config.secret =ENV['WARDEN_JWT_SECRET_KEY']
end

Important: You are encouraged to use a dedicated secret key, different than others in use in your application. If several components share the same secret key, chances that a vulnerability in one of them has a wider impact increase. Also, never share your secrets pushing it to a remote repository, you are better off using an environment variable like in the example.

Currently, HS256 algorithm is the one in use.

Warden scopes configuration

You have to map the warden scopes that will be authenticatable through JWT, with the user repositories from where these scope user records can be fetched. If a string is supplied, the user repository will first be looked up as a constant.

For instance:

config.mappings = { user:UserRepository }

For this example, UserRepository must implement a method find_for_jwt_authentication that takes as argument the sub claim in the JWT payload. This method should return a user record from :user scope:

User records must implement a jwt_subject method returning what should be encoded in the sub claim on dispatch time. Be aware that what is returned must be coercible to string in order to conform with RFC7519 standard for sub claim.

User=Struct.new(:id, :name)
defjwt_subject
id
endend

User records may also implement a jwt_payload method, which gives it a chance to add something to the JWT payload:

defjwt_payload
{ 'foo' => 'bar' }
end

Just when a token is going to be dispatched to a client, a hook method on_jwt_dispatch is invoked, only when it exist, on the user record. This method takes the token and the payload as arguments.

defon_jwt_dispatch(token, payload)
# Do somethingend

Middlewares addition

You need to add Warden::JWTAuth::Middleware to your rack middlewares stack. Actually, it is just a wrapper which adds two middlewares that do the actual job: dispatching tokens and revoking tokens.

Token dispatch configuration

You need to tell which requests will dispatch tokens for the user that has been previously authenticated (usually through some other warden strategy, such as one requiring username and email parameters).

To configure it, you must provide a bidimensional array, each item being an array of two elements: the request method and a regular expression that must match the request path.

For example:

config.dispatch_requests = [
['POST', %r{^/sign_in$}]
]

Important: You are encouraged to delimit your regular expression with ^ and $ to avoid unintentional matches.

Tokens will be returned in the Authorization response header, with format Bearer #{token}.

Requests authentication

Once you have a valid token, you can authenticate following requests providing the token in the Authorization request header, with format Bearer #{token}.

Revocation configuration

You need to tell which requests will revoke incoming JWT tokens.

To configure it, you must provide a bidimensional array, each item being an array of two elements: the request method and a regular expression that must match the request path.

For example:

config.revocation_requests = [
['DELETE', %r{^/sign_out$}]
]

Important: You are encouraged to delimit your regular expression with ^ and $ to avoid unintentional matches.

Besides, you need to configure which revocation strategy will be used for each scope. If a string is supplied, the revocation strategy will first be looked up as a constant.

config.revocation_strategies = { user:RevocationStrategy }

The implementation of the revocation strategy is also on your side. They just need to implement two methods: jwt_revoked? and revoke_jwt, both of them accepting as parameters the JWT payload and the user record, in this order.

moduleRevocationStrategydefself.jwt_revoked?(payload, user)
# Does something to check whether the JWT token is revoked for given userenddefself.revoke_jwt(payload, user)
# Does something to revoke the JWT token for given userendend

Requesting client validation

Authentication will be refused if a client requesting to be authenticated through a token is not the same to which it was originally issued. To do so, the content of the header JWT_AUD (configurable via config.aud_header) is stored as aud claim. If you don't want to differentiate between clients, you don't need to provide that header.

Important: Be aware that this workflow is not bullet proof. In some scenarios a user can handcraft the request headers, therefore being able to impersonate any client. In such cases you could need something more robust, like an OAuth workflow with client id and client secret.

Development

There are docker and docker-compose files configured to create a development environment for this gem. So, if you use Docker you only need to run:

docker-compose up -d

An then, for example:

docker-compose exec app rspec

This gem uses overcommit to execute some code review engines. If you submit a pull request, it will be executed in the CI process. In order to set it up, you need to do:

bundle install --gemfile=.overcommit_gems.rb
overcommit --sign
overcommit --run # To test if it works