GithubHelp home page GithubHelp logo

waiting-for-dev / devise-jwt Goto Github PK

View Code? Open in Web Editor NEW
1.2K 17.0 130.0 311 KB

JWT token authentication with devise and rails

License: MIT License

Ruby 99.30% Shell 0.27% Dockerfile 0.43%
devise jwt rails ruby-gem authentication

devise-jwt's Introduction

Devise::JWT

Gem Version Build Status Code Climate Test Coverage

devise-jwt is a Devise extension which uses JWT tokens for user authentication. It follows secure by default principle.

This gem is just a replacement for cookies when these can't be used. As with cookies, a devise-jwt token 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:

devise-jwt is just a thin layer on top of warden-jwt_auth that configures it to be used out of the box with Devise and Rails.

Upgrade notes

v0.7.0

Since version v0.7.0 Blacklist revocation strategy has been renamed to Denylist while Whitelist has been renamed to Allowlist.

For Denylist, you only need to update the include line you're using in your revocation strategy model:

# include Devise::JWT::RevocationStrategies::Blacklist # before
include Devise::JWT::RevocationStrategies::Denylist

For Allowlist, you need to update the include line you're using in your user model:

# include Devise::JWT::RevocationStrategies::Whitelist # before
include Devise::JWT::RevocationStrategies::Allowlist

You also have to rename your WhitelistedJwt model to AllowlistedJwt, rename model/whitelisted_jwt.rb to model/allowlisted_jwt.rb and change the underlying database table to allowlisted_jwts (or configure the model to keep using the old name).

Installation

Add this line to your application's Gemfile:

gem 'devise-jwt'

And then execute:

$ bundle

Or install it yourself as:

$ gem install devise-jwt

Usage

First, you need to configure Devise to work in an API application. You can follow the instructions in this project wiki page Configuring Devise for APIs (you are more than welcome to improve them).

Secret key configuration

You have to configure the secret key that will be used to sign generated tokens. You can do it in the Devise initializer:

Devise.setup do |config|
  # ...
  config.jwt do |jwt|
    jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
  end
end

If you are using Encrypted Credentials (Rails 5.2+), you can store the secret key in config/credentials.yml.enc.

Open your credentials editor using bin/rails credentials:edit and add devise_jwt_secret_key.

Note you may need to set $EDITOR depending on your specific environment.

# Other secrets...

# Used as the base secret for Devise JWT
devise_jwt_secret_key: abc...xyz

Add the following to the Devise initializer.

Devise.setup do |config|
  # ...
  config.jwt do |jwt|
    jwt.secret = Rails.application.credentials.devise_jwt_secret_key!
  end
end

Important: You are encouraged to use a secret different than your application secret_key_base. It is quite possible that some other component of your system is already using it. If several components share the same secret key, chances that a vulnerability in one of them has a wider impact increase. In rails, generating new secrets is as easy as rails secret. 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. You may configure a matching secret and algorithm name to use a different one (see ruby-jwt to see which are supported):

Devise.setup do |config|
  # ...
  config.jwt do |jwt|
    jwt.secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_secret_key!)
    jwt.algorithm = Rails.application.credentials.devise_jwt_algorithm!
  end
end

If the algorithm is asymmetric (e.g. RS256) which necessitates a different decoding secret, configure the decoding_secret setting as well:

Devise.setup do |config|
  # ...
  config.jwt do |jwt|
    jwt.secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_private_key!)
    jwt.decoding_secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_public_key!)
    jwt.algorithm = 'RS256' # or some other asymmetric algorithm
  end
end

Model configuration

You have to tell which user models you want to be able to authenticate with JWT tokens. For them, the authentication process will be like this:

  • A user authenticates through Devise create session request (for example, using the standard :database_authenticatable module).
  • If the authentication succeeds, a JWT token is dispatched to the client in the Authorization response header, with format Bearer #{token} (tokens are also dispatched on a successful sign up).
  • The client can use this token to authenticate following requests for the same user, providing it in the Authorization request header, also with format Bearer #{token}
  • When the client visits Devise destroy session request, the token is revoked.

See request_formats configuration option if you are using paths with a format segment (like .json) in order to use it properly.

As you see, unlike other JWT authentication libraries, it is expected that tokens will be revoked by the server. I wrote about why I think JWT revocation is needed and useful.

An example configuration:

class User < ApplicationRecord
  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: Denylist
end

If you need to add something to the JWT payload, you can do it by defining a jwt_payload method in the user model. It must return a Hash. For instance:

def jwt_payload
  { 'foo' => 'bar' }
end

You can add a hook method on_jwt_dispatch on the user model. It is executed when a token dispatched for that user instance, and it takes token and payload as parameters.

def on_jwt_dispatch(token, payload)
  do_something(token, payload)
end

Note: if you are making cross-domain requests, make sure that you add Authorization header to the list of allowed request headers and exposed response headers. You can use something like rack-cors for that, for example:

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://your.frontend.domain.com'
    resource '/api/*',
      headers: %w(Authorization),
      methods: :any,
      expose: %w(Authorization),
      max_age: 600
  end
end

Session storage caveat

If you are working with a Rails application that has session storage enabled and a default Devise setup, chances are the same origin requests will be authenticated from the session regardless of a token being present in the headers or not.

This is so because of the following default Devise workflow:

  • When a user signs in with :database_authenticatable strategy, the user is stored in the session unless one of the following conditions is met:
    • Session is disabled.
    • Devise config.skip_session_storage includes :params_auth.
    • Rails Request forgery protection handles an unverified request (but this is usually deactivated for API requests).
  • Warden (the engine below Devise), authenticates any request that the user has in the session without requiring a strategy (:jwt_authenticatable in our case).

So, if you want to avoid this caveat you have five options:

  • Disable the session. If you are developing an API, you probably don't need it. In order to disable it, change config/initializers/session_store.rb to:

    Rails.application.config.session_store :disabled

    Notice that if you created the application with the --api flag you already have the session disabled.

  • If you still need the session for any other purpose, disable :database_authenticatable user storage. In config/initializers/devise.rb:

    config.skip_session_storage = [:http_auth, :params_auth]
  • If you are using Devise for another model (e.g. AdminUser) and doesn't want to disable session storage for Devise entirely, you can disable it on a per-model basis:

    class User < ApplicationRecord
      devise :database_authenticatable #, your other enabled modules...
      self.skip_session_storage = [:http_auth, :params_auth]
    end
  • If you need the session for some of the controllers, you are able to disable it at the controller level for those controllers which don't need it:

    class AdminsController < ApplicationController
      before_action :drop_session_cookie
    
      private
    
      def drop_session_cookie
        request.session_options[:skip] = true
      end
  • As the last option you can tell Devise to not store the user in the Warden session if you override default Devise SessionsController with your own one, and pass store: false attribute to the sign_in, sign_in_and_redirect, bypass_sign_in methods:

    sign_in user, store: false

Revocation strategies

devise-jwt comes with three revocation strategies out of the box. Some of them are implementations of what is discussed in the blog post JWT Revocation Strategies, where I also talk about their pros and cons.

JTIMatcher

Here, the model class acts as the revocation strategy. It needs a new string column named jti to be added to the user. jti stands for JWT ID, and it is a standard claim meant to uniquely identify a token.

It works like the following:

  • When a token is dispatched for a user, the jti claim is taken from the jti column in the model (which has been initialized when the record has been created).
  • At every authenticated action, the incoming token jti claim is matched against the jti column for that user. The authentication only succeeds if they are the same.
  • When the user requests to sign out its jti column changes, so that provided token won't be valid anymore.

In order to use it, you need to add the jti column to the user model. So, you have to set something like the following in a migration:

def change
  add_column :users, :jti, :string, null: false
  add_index :users, :jti, unique: true
  # If you already have user records, you will need to initialize its `jti` column before setting it to not nullable. Your migration will look this way:
  # add_column :users, :jti, :string
  # User.all.each { |user| user.update_column(:jti, SecureRandom.uuid) }
  # change_column_null :users, :jti, false
  # add_index :users, :jti, unique: true
end

Important: You are encouraged to set a unique index in the jti column. This way we can be sure at the database level that there aren't two valid tokens with same jti at the same time.

Then, you have to add the strategy to the model class and configure it accordingly:

class User < ApplicationRecord
  include Devise::JWT::RevocationStrategies::JTIMatcher

  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: self
end

Be aware that this strategy makes uses of jwt_payload method in the user model, so if you need to use it don't forget to call super:

def jwt_payload
  super.merge('foo' => 'bar')
end

Denylist

In this strategy, a database table is used as a list of revoked JWT tokens. The jti claim, which uniquely identifies a token, is persisted. The exp claim is also stored to allow the clean-up of stale tokens.

In order to use it, you need to create the denylist table in a migration:

def change
  create_table :jwt_denylist do |t|
    t.string :jti, null: false
    t.datetime :exp, null: false
  end
  add_index :jwt_denylist, :jti
end

For performance reasons, it is better if the jti column is an index.

Note: if you used the denylist strategy before version 0.4.0 you may not have the field exp. If not, run the following migration:

class AddExpirationTimeToJWTDenylist < ActiveRecord::Migration
  def change
    add_column :jwt_denylist, :exp, :datetime, null: false
  end
end

Then, you need to create the corresponding model and include the strategy:

class JwtDenylist < ApplicationRecord
  include Devise::JWT::RevocationStrategies::Denylist

  self.table_name = 'jwt_denylist'
end

Last, configure the user model to use it:

class User < ApplicationRecord
  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
end

Allowlist

Here, the model itself also acts as a revocation strategy, but it needs to have a one-to-many association with another table which stores the tokens (in fact their jti claim, which uniquely identifies them) that are valid for each user record.

The workflow is as the following:

  • Once a token is dispatched for a user, its jti claim is stored in the associated table.
  • At every authentication, the incoming token jti is matched against all the jti associated to that user. The authentication only succeeds if one of them matches.
  • On a sign out, the token jti is deleted from the associated table.

In fact, besides the jti claim, the aud claim is also stored and matched at every authentication. This, together with the aud_header configuration parameter, can be used to differentiate between clients or devices for the same user.

The exp claim is also stored to allow the clean-up of staled tokens.

In order to use it, you have to create the associated table and model. The association table must be called allowlisted_jwts:

def change
  create_table :allowlisted_jwts do |t|
    t.string :jti, null: false
    t.string :aud
    # If you want to leverage the `aud` claim, add to it a `NOT NULL` constraint:
    # t.string :aud, null: false
    t.datetime :exp, null: false
    t.references :your_user_table, foreign_key: { on_delete: :cascade }, null: false
  end

  add_index :allowlisted_jwts, :jti, unique: true
end

Important: You are encouraged to set a unique index in the jti column. This way we can be sure at the database level that there aren't two valid tokens with the same jti at the same time. Defining foreign_key: { on_delete: :cascade }, null: false on t.references :your_user_table helps to keep referential integrity of your database.

And then, the model:

class AllowlistedJwt < ApplicationRecord
end

Finally, include the strategy in the model and configure it:

class User < ApplicationRecord
  include Devise::JWT::RevocationStrategies::Allowlist

  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: self
end

Be aware that this strategy makes uses of on_jwt_dispatch method in the user model, so if you need to use it don't forget to call super:

def on_jwt_dispatch(token, payload)
  super
  do_something(token, payload)
end

Null strategy

A null object pattern strategy, which does not revoke tokens, is provided out of the box just in case you are absolutely sure you don't need token revocation. It is recommended not to use it.

class User < ApplicationRecord
  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null
end

Custom strategies

You can also implement your own strategies. They just need to implement two methods: jwt_revoked? and revoke_jwt, both of them accept the JWT payload and the user record as parameters, in this order.

For instance:

module MyCustomStrategy
  def self.jwt_revoked?(payload, user)
    # Does something to check whether the JWT token is revoked for given user
  end

  def self.revoke_jwt(payload, user)
    # Does something to revoke the JWT token for given user
  end
end

class User < ApplicationRecord
  devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: MyCustomStrategy
end

Testing

Models configured with :jwt_authenticatable usually won't be retrieved from the session. For this reason, sign_in Devise testing helper methods won't work as expected.

What you need to do to authenticate test environment requests is the same that you will do in production: to provide a valid token in the Authorization header (in the form of Bearer #{token}) at every request.

There are two ways you can get a valid token:

  • Inspecting the Authorization response header after a valid sign in request.
  • Manually creating it.

The first option tests the real workflow of your application, but it can slow things if you perform it at every test.

For the second option, a test helper is provided in order to add the Authorization name/value pair to given request headers. You can use it as in the following example:

# First, require the helper module
require 'devise/jwt/test_helpers'

# ...

  it 'tests something' do
    user = fetch_my_user()
    headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
    # This will add a valid token for `user` in the `Authorization` header
    auth_headers = Devise::JWT::TestHelpers.auth_headers(headers, user)

    get '/my/end_point', headers: auth_headers

    expect_something()
  end

Usually you will wrap this in your own test helper.

Configuration reference

This library can be configured calling jwt on Devise config object:

Devise.setup do |config|
  config.jwt do |jwt|
    # ...
  end
end

secret

Secret key is used to sign generated JWT tokens. You must set it.

rotation_secret

Allow rotating secrets. Set a new value to secret and copy the old secret to rotation_secret.

expiration_time

Number of seconds while a JWT is valid after its generation. After that, it won't be valid anymore, even if it hasn't been revoked.

Defaults to 3600 seconds (1 hour).

dispatch_requests

Besides the create session one, there are additional requests where JWT tokens should be dispatched.

It must be 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:

jwt.dispatch_requests = [
                          ['POST', %r{^/dispatch_path_1$}],
                          ['GET', %r{^/dispatch_path_2$}],
                        ]

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

revocation_requests

Besides the destroy session one, there are additional requests where JWT tokens should be revoked.

It must be 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:

jwt.revocation_requests = [
                            ['DELETE', %r{^/revocation_path_1$}],
                            ['GET', %r{^/revocation_path_2$}],
                          ]

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

request_formats

Request formats that must be processed (in order to dispatch or revoke tokens).

It must be a hash of Devise scopes as keys and an array of request formats as values. When a scope is not present or if it has a nil item, requests without format will be taken into account.

For example, with following configuration, user scope would dispatch and revoke tokens in json requests (as in /users/sign_in.json), while admin_user would do it in xml and with no format (as in /admin_user/sign_in.xml and /admin_user/sign_in).

jwt.request_formats = {
                        user: [:json],
                        admin_user: [nil, :xml]
                      }

By default, only requests without format are processed.

token_header

Request/response header which will transmit the JWT token.

Defaults to 'Authorization'

issuer

Expected issuer claim. If present, it will be checked against the incoming token issuer claim and authorization will be skipped if they don't match.

Defaults to nil.

aud_header

Request header which content will be stored to the aud claim in the payload.

It is used to validate whether an incoming token was originally issued to the same client, checking if aud and the aud_header header value match. 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.

Defaults to JWT_AUD.

token_header

Request header containing the token in the format of Bearer #{token}.

Defaults to Authorization.

issuer

The issuer claim in the token.

If present, it will be checked against the incoming token issuer claim and authorization will be skipped if they don't match.

Defaults to nil.

jwt.issuer = 'http://myapp.com'

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

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/waiting-for-dev/devise-jwt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Release Policy

devise-jwt follows the principles of semantic versioning.

License

The gem is available as open source under the terms of the MIT License.

devise-jwt's People

Contributors

avogel3 avatar christophweegen avatar codesoda avatar danielricecodes avatar dependabot[bot] avatar dokuo9 avatar elthariel avatar eric-guo avatar f3ndot avatar jcavalieri avatar jelaniwoods avatar keithmattix avatar kopylovvlad avatar laiskajoonas avatar lauramosher avatar lee-humes avatar lucasarruda avatar m-nakamura145 avatar markhallen avatar nicolasleger avatar olleolleolle avatar osheroff avatar pcriv avatar petergoldstein avatar pjg avatar rudyonrails avatar seuros avatar syed-naufal-mahmood avatar urielhdz avatar waiting-for-dev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

devise-jwt's Issues

[Question] Multiple tokens simultaneously

After toying with the gem for a while (using postman app) thinking how to renew the token before expires, I noticed that after each sign in a new token is generated but the old ones keep working even after more than 5 other tokens have been generated. Is this the desired behavior?

Wouldn't blacklist table just keep growing?

Question about blacklist revocation strategy. From the readme it looks like that would be a table that would grow and would need occasional deleting. To assist cleaning out that table would it be useful to add the token's expiration along with the JTI?

I know I can have a custom strategy, just thought I would pose the question to see if that might be an enhancement to consider.

Thanks,
John

MappingInspector does not account for path with empty scope.

When devise is configured to use short urls (ex: /login for /users/sign_in and /logout for /users/sign_out), the paths generated by MappingInspector are incorrect.

For this setup in routes.rb -
devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout'}

MappingInspector path generates //login instead of /login.

Can the README include instructions on creating session requests as an API?

First, this gem is awesome, so thank you for your work on it! ๐Ÿ‘

One thing that would be very helpful would be if you could expand on this point in the docs:

A user authenticates through devise create session request (for example, using the standard :database_authenticatable module).

Devise doesn't seem to respond to JSON requests out-of-the-box, and most of the links and tutorials on doing so revolve around old versions of Devise or Rails. When I try and authenticate at /sign_in via JSON, I get hit with:

ActionController::UnknownFormat at /clients/sign_in

Reviewing your example, I saw that adding

respond_to :json
to my application_controller.json allows me to solve the problem, but this isn't a great workaround because actions like /sign_out/ then return blank when you hit them with accept: json. I'm confused why there isn't a gem or agreed upon way to overwrite the devise controllers to respond to both html and json formats, but maybe there are some resources you could toss in the readme that would make that part less confusing to novices to devise like myself. Apologies if this is out of the scope of the gem; having a hard time finding resources on getting Devise API-ready.

rubygems?

Hi @waiting-for-dev,

It looks like the rubygems for this project is behind. Do you plan on updating rubygems? Or should we I just put in a github ref in my Gemfile?

Thanks,
John

Testing Login with CURL

Hi,

I installed the devise-jwt gem as mentioned in the readme document, but currently I have some problems testing the setup. For a test, I created a user. If I use the device web interface, the user can log in. But if I use the json Interface, this does not work for me. I tried the following CURL requests:

curl -i -X POST http://localhost:3000/users/sign_in.json -d "[email protected]&password=111111"

But it does not work for me.

HTTP/1.1 401 Unauthorized Content-Type: application/json; charset=utf-8 Cache-Control: no-cache X-Request-Id: 6649996c-33ab-439c-87d3-f7b529f08857 X-Runtime: 0.024306 Vary: Origin Transfer-Encoding: chunked

and on the server

Started POST "/users/sign_in.json" for 127.0.0.1 at 2017-09-05 03:14:44 +0200 Processing by Devise::SessionsController#create as JSON Parameters: {"email"=>"[email protected]", "password"=>"[FILTERED]"} Completed 401 Unauthorized in 5ms (ActiveRecord: 0.0ms)

Can some one help me?

Missing authorization header ?

Hey, first of thanks for making this gem. I am working on the configuration aspects however I am not able to see the Authorization header using cURL or even the normal HTTP login form. Authentication is working just fine as expected. However, i am not seeing the Authorization: Bearer #{token} in the HTTP response

`curl -vvv -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' 'http://localhost/login?account%5Bemail%5D=user%40example.go&account%5Bpassword%5D=password'

  • Hostname was NOT found in DNS cache
  • Trying 127.0.0.1...
  • Connected to localhost (127.0.0.1) port 80 (#0)

POST /login?account%5Bemail%5D=user%40example.gov&account%5Bpassword%5D=password HTTP/1.1
User-Agent: curl/7.35.0
Host: localhost
Content-Type: application/json
Accept: application/json

< HTTP/1.1 201 Created
< Content-Type: application/json; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Status: 201 Created
< Location: /
< Cache-Control: max-age=0, private, must-revalidate
< ETag: W/"76dbae3821e4e2fa727009e0f5b8238b"
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Runtime: 0.259600
< X-Request-Id: 35923e38-342a-411b-b347-f6814939c116
< Date: Thu, 06 Apr 2017 16:39:53 GMT
< Set-Cookie: my_app=bd95d0c2945e03bd1abc10e1644d584b; path=/; expires=Thu, 06 Apr 2017 18:09:53 -0000; HttpOnly
< X-Powered-By: Phusion Passenger 5.1.2

  • Server nginx/1.10.2 + Phusion Passenger 5.1.2 is not blacklisted
    < Server: nginx/1.10.2 + Phusion Passenger 5.1.2
    <
  • Connection #0 to host localhost left intact
    {"data":{"id":"1","type":"administrators","attributes":{"active":true,"name":"Markus A. Kobold","email":"[email protected]","phone":"(555) 555-555","office":"xxx","created-at":"2012-04-24T19:49:01.000Z","updated-at":"2017-04-06T16:39:53.080Z","preferences":{"locale":{"tz":"Pacific Time (US \u0026 Canada)","date-format":"relative"},"source":{"sub-fellow-forms":false}},"auth":{"payload":{"jti":"1864e178-7cf8-41f6-a698-2d2e64b21825"}}}}}`

Using the JTI Matcher for revocation and it's all initialized properly. So i am not sure why the Authorization header isn't returning the JWT token for future requests.

Cant install the gem in a fresh new Rails 5.1.0 project

I just created a new project in rails and when adding the gem I receive the following error:

Bundler could not find compatible versions for gem "railties":
  In Gemfile:
    devise-jwt (~> 0.1.1) was resolved to 0.1.1, which depends on
      devise (~> 4.0) was resolved to 4.0.0, which depends on
        railties (< 5.1, >= 4.1.0)

    rails (~> 5.1.0) was resolved to 5.1.0, which depends on
      railties (= 5.1.0)

Authorization headers not returned after sign_in

Hello, no matter what I try the JWT token is not returned after Sign in

image

user.rb

class User < ActiveRecord::Base
devise :database_authenticatable, :recoverable, :rememberable,
:validatable, :lockable, :registerable, :confirmable, :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null

routes.rb

devise_for :users, controllers: {
sessions: 'sessions',
passwords: 'passwords',
unlocks: 'unlocks',
registrations: 'registrations',
confirmations: 'confirmations'
}, skip: [:sessions, :passwords, :unlocks, :registrations, :confirmations]

as :user do
get 'sign_in' => 'sessions#new', as: :new_user_session
post 'sign_in' => 'sessions#create', as: :user_session
delete 'sign_out' => 'sessions#destroy', as: :destroy_user_session
get 'sign_up' => 'registrations#new', as: :new_user_registration
post 'sign_up' => 'registrations#create', as: :user_registration
get 'reconfirm' => 'confirmations#new', as: :new_confirmation
post 'confirm' => 'confirmations#create', as: :confirmation
get 'confirm' => 'confirmations#show'
scope '/account' do
# password reset
get 'reset_password' => 'passwords#new', as: :new_user_password
put 'reset_password' => 'passwords#update', as: :user_password
post 'reset_password' => 'passwords#create'
get 'reset-password/change', to: 'passwords#edit', as: :edit_user_password

  # unlocks
  post 'unlock',     to: 'unlocks#create', as: :unlock
  get  'unlock/new', to: 'unlocks#new',    as: :new_unlock
  get  'unlock',     to: 'unlocks#show'
end

end

devise.rb

Devise.setup do |config|

config.jwt do |jwt|
jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
jwt.request_formats = {
user: [:json]
}
end

bundle exec rake middleware

use Airbrake::UserInformer
use Rack::Cors
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f98542a8660
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use BetterErrors::Middleware
use Airbrake::Rails::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Warden::Manager
use Warden::JWTAuth::Middleware
run OdinDemo::Application.routes

The sign_in is successful , but still no tokens in the headers. Any ideas how to debug this? Thank you!

Authorization header not sent on sign_in.json

so i've setup the sessions controller to responds to json format request, but upon successfull login, the header Authorization is not set...

see screenshot
screen shot 2017-04-28 at 8 48 33 pm

but if its a regular sign in, like it redirects upon successful authentication, its able to set the Authoriztion header
screen shot 2017-04-28 at 8 50 42 pm

Application with API & GUI: Session cookie is set, so clients can use this to authenticate insteaded of JWT

Hey,

In my application I have ActionController::API controllers and standard ActionController::Base subclasses. I want my API users to only be able to authenticate via JWT, however (I guess) since the middleware is still loaded in the API controller context, the initial session creation still sets the cookie on the client.

In practice, this means that the API user can use the cookie to authenticate instead of the JWT. I would prefer this not to be the case, and it's not entirely clear how best to disable session management in this context.

Appreciate this is possibly more of a rails/devise question than a devise-jwt question, but hoping you might have some pointers for my use case?

no implicit conversion of nil into String

def sign_hmac(algorithm, msg, key)
    OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
  end

  def base64url_encode(str)

TypeError in Devise::SessionsController#create

i got this issue after integrating devise jwt in my application. i am using JTIMatcher Strategy. i am able to create users successfully with unique jti IDs but unable to sign in. can anybody help me?
Plus i am using confirmable in user model. is that creating an issue or what?

My user model is as below:

class User < ApplicationRecord

include Devise::JWT::RevocationStrategies::JTIMatcher
 # Include default devise modules. Others available are:
 # :confirmable, :lockable, :timeoutable and :omniauthable
 devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :trackable, :validatable,
        :confirmable,
        :jwt_authenticatable, jwt_revocation_strategy: self
end 

plus i am using

postgres

database.
my recent migration is:

class AddJtiToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :jti, :string, null: false
    add_index :users, :jti, unique: true
  end
end

Adding validation removes Authorization from Header

Authorization is being removed from header when I add a validation to the User model:

Target version: 0.3.0

JWT and Devise config for User model:

  include Devise::JWT::RevocationStrategies::JTIMatcher
  devise :database_authenticatable,
         :recoverable,
         :registerable,
         :validatable,
         :jwt_authenticatable, jwt_revocation_strategy: self

  def jwt_payload
    super
  end

Header before I add validation:

{"Location"=>"/", "Content-Type"=>"text/plain; charset=utf-8", "Authorization"=>"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJiZTRkNzNlNy0xMjc3LTQyNjUtYWY2MC00ODNjMTBhMDBhYzQiLCJzdWIiOiIxMjAiLCJzY3AiOiJ1c2VyIiwiaWF0IjoxNDk3OTYzNTcwLCJleHAiOjE1MDA1NTU1NzB9.myZ5gDGqFn6AbpDVbIBd5ZQqLPTL4AxqdC5KsYcRQMg", "ETag"=>"W/\"36a9e7f1c95b82ffb99743e0c5c4ce95\"", "Cache-Control"=>"max-age=0, private, must-revalidate", "X-Request-Id"=>"d2f4dc7d-cf30-403a-8a6c-353daeab64c3", "X-Runtime"=>"0.015489", "Vary"=>"Origin", "Content-Length"=>"1"}

Validation (the only change I do):

  validates_presence_of :first_name

Header after I add the validation:

{"Content-Type"=>"text/plain; charset=utf-8", "ETag"=>"W/\"36a9e7f1c95b82ffb99743e0c5c4ce95\"", "Cache-Control"=>"max-age=0, private, must-revalidate", "X-Request-Id"=>"860747c0-0529-4492-855c-778698972599", "X-Runtime"=>"0.015620", "Vary"=>"Origin", "Content-Length"=>"1"}

P.S.: Setting up request_formats or dispatch_requests doesn't help. Existing validation on email doesn't affect it though.

Sign in Failed

Rails 5.1.1
Ruby 2.4.1

Tested on both devise-jwt 0.1.1 and 0.3.0

Tried to signup a user, got a success response with the jti in response

Next tried to sign_in by passing the generated jti in the authorization header.
But i get a response of Failed to Login

I'm attaching the postman images of the same.

screen shot 2017-06-20 at 14 40 02

screen shot 2017-06-20 at 14 40 12

Unable to switch to JWT authentication even after following instructions

I have the following in my user model, but still when I login the authentication happens using sessions.

  devise :database_authenticatable, :jwt_authenticatable, :registerable, :confirmable,
         :rememberable, :validatable, :recoverable, :lockable, :trackable,
         :timeoutable, timeout_in: 7.days, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null

Errors Integrating with project that does not use User Model

Hello, I recently wanted to try using the devise-jwt gem since it seems to be the only gem handling both overriding Devise's natural paths, and handling revoking JWTs after sign-out.

I am currently using the Employee model instead of the user model.

include Devise::JWT::RevocationStrategies::JTIMatcher
devise :invitable, :database_authenticatable, :timeoutable, :omniauthable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable,
         :jwt_authenticatable, jwt_revocation_strategy: self

I have defined the jti field in the employee table in the database and added null: false, index, and unique: true calls to the field as well.

I also added the configuration options to the devise initializer.

According to the documentation, these should be all of the steps required to make this gem function the way it is supposed to. However, at this time, this setup causes the basic helpers provided by devise itself to break and no longer function. I now have 600+ failing tests that all claim:

V1::EmployeesController Recruiter Admin Capabilities should have a current employee
Failure/Error: expect(subject.current_employee).to_not eq(nil)

 NoMethodError:
   undefined method `current_employee' for #<V1::EmployeesController:0x007fcb1d3feb08>
 # ./spec/controllers/v1/employees_controller_spec.rb:13:in `block (3 levels) in <top (required)>'

or:

V1::EmployeesController Recruiter Admin Capabilities GET #show Valid Employee JSON Request should respond with successful HTTP Code 200
Failure/Error: get :show, id: @employee.id, format: :json

 NoMethodError:
   undefined method `authenticate_employee!' for #<V1::EmployeesController:0x007fcb1d4a1b28>
 # ./spec/controllers/v1/employees_controller_spec.rb:32:in `block (6 levels) in <top (required)>'

Note: I do have the devise controller helpers included in my spec_helper.rb and these tests were indeed passing up until setup of devise-jwt.

is there a step that I forgot? Or do I need to create these methods in the application controller to overwrite devise defaults? What is happening here?

Refresh tokens

Enforcing the user to re-login every time the JWT token expires would be annoying on a web experience. I wouldn't want to set a long expiration time on JWT token for security reasons.

How would you recommend going about this?

Invalid token on sessions#destroy

I have encountered a bug while writing request tests. I wanted to make sure that devise-jwt handles corner cases such as the invalid format of JWT token or invalid JWT token but unfortunately it's not.

For invalid format it returns a JWT::DecodeError with 500 status code.
For invalid JWT it returns a JWT::VerificationError with 500 status code.

I wanted somehow to find a workaround by implementing ExceptionHandler with rescure_from for these two errors.

module ExceptionHandler
  extend ActiveSupport::Concern

  included do
    rescue_from JWT::DecodeError, with: :unauthorized_request
    rescue_from JWT::VerificationError, with: :unauthorized_request
  end

  private

  def unauthorized_request(e)
    render json: { message: e.message }, status: :unauthorized
  end
end

I have added it to my SessionController and overridden destroy action (I had to add verify_signed_out_user in order to override destroy method):

module Api
  module V1
    class SessionsController < Devise::SessionsController
      skip_before_action :verify_signed_out_user
      include ExceptionHandler

      def destroy
        binding.pry
      end
    end
  end
end

At this moment every request triggers my binding.pry but then it seems that gem does own logic on top it, so I'm not able to rescue from these errors.

EDIT:
Okay, I make a workaround by writing custom middleware which catches those errors.

JTI Strategy for multiple clients (multiple web laptops, phones etc)

Just wondering what your thoughts on using a JTI strategy for users who can have access from multiple clients devices. For example:

  1. User is signed in on laptop at home in Chrome via web client, web client stores JWT token there
  2. User is signed in on laptop at work in Safari via web client, web client stores JWT Token in localStorage there
  3. User is signed in on their mobile phone via a native app, app stores JWT token in storage on the phone.

Would you have a jti for each uniquely identifiable client (some function of device and ip Im guessing) per user record in the database?

JWT not returned on register

When i register a new user, using signup endpoint, the user is returned with the jti.
But checking the response headers i don't see the Authorization header in it.

But when the user is signed in using the sign_in link it does return the Authorization header.
Could there be anything wrong in my setup? Or is there any other configuration i need to set to get the header in signup too?

Authorization header is not being returned

I'm scratching my head with this one and have seen people have had similar issues. I wondering if these doesn't work if you have Devise custom controllers. Here is my code (I've omitted some details for brevity)

config/initializers/devise.rb

config.jwt do |jwt|
  jwt.secret = 'c6977142e3d968eb45a955b89b095f55cc8e2640e159682d6a49bfe3c0c2a937a6f4420a181e962dd0cb64233b93756ad34fd6dc8a311d2045b5c06bcbc828e6'
  jwt.request_formats = {
                        landlord: [:json],
                      }
  jwt.dispatch_requests = [
                          ['POST', %r{^/api/lanlords/sign_in.json$}]
                        ]
  end

config/routes.rb

devise_for :landlords, defaults: { format: :json }, :controllers => {:sessions => "landlords/sessions"},
:path => '/api/landlords'

config/application.rb

class Application < Rails::Application
      config.middleware.insert_before 0, Rack::Cors do
       allow do
    origins "localhost:3000", "127.0.0.1:3000", "*"
    resource "*",
      :headers => :any,
      :expose  => [
        "X-Requested-With",
        "Content-Type",
        "Authorization",
        "Accept",
        "Client-Security-Token",
        "Accept-Encoding",
        "iat",
        "exp",
        "jti"
      ],
      :methods => [:get, :post, :options, :delete, :put, :head]
  end

gemfile

gem 'devise'
gem 'devise-jwt', '~> 0.3.0'

request

screen shot 2017-08-31 at 07 23 10

Undefined method "update_column" when sign_out

Hi, I use devise 4.3.0, devise-jwt 3.0 with mongoid 6.2.0. My auth model is Client.
When I try to logout, I receive the fallowing error:

NoMethodError (undefined method `update_column' for #<Client:0x007f864e99b548>
Did you mean?  updateable?):
  
devise-jwt (0.3.0) lib/devise/jwt/revocation_strategies/jti_matcher.rb:29:in `revoke_jwt'
warden-jwt_auth (0.1.3) lib/warden/jwt_auth/token_revoker.rb:16:in `call'
warden-jwt_auth (0.1.3) lib/warden/jwt_auth/middleware/revocation_manager.rb:30:in `revoke_token'
warden-jwt_auth (0.1.3) lib/warden/jwt_auth/middleware/revocation_manager.rb:21:in `call'
rack (2.0.3) lib/rack/builder.rb:153:in `call'
warden-jwt_auth (0.1.3) lib/warden/jwt_auth/middleware.rb:23:in `call'
warden (1.2.7) lib/warden/manager.rb:36:in `block in call'
warden (1.2.7) lib/warden/manager.rb:35:in `catch'
warden (1.2.7) lib/warden/manager.rb:35:in `call'
rack (2.0.3) lib/rack/etag.rb:25:in `call'
rack (2.0.3) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.3) lib/rack/head.rb:12:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'
activesupport (5.1.3) lib/active_support/callbacks.rb:97:in `run_callbacks'
actionpack (5.1.3) lib/action_dispatch/middleware/callbacks.rb:24:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.1.3) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.1.3) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.1.3) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.1.3) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.1.3) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.1.3) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
request_store_rails (1.0.3) lib/request_store_rails/middleware.rb:17:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/request_id.rb:25:in `call'
rack (2.0.3) lib/rack/runtime.rb:22:in `call'
activesupport (5.1.3) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.3) lib/action_dispatch/middleware/static.rb:125:in `call'
rack (2.0.3) lib/rack/sendfile.rb:111:in `call'
railties (5.1.3) lib/rails/engine.rb:522:in `call'
puma (3.9.1) lib/puma/configuration.rb:224:in `call'
puma (3.9.1) lib/puma/server.rb:602:in `handle_request'
puma (3.9.1) lib/puma/server.rb:435:in `process_client'
puma (3.9.1) lib/puma/server.rb:299:in `block in run'
puma (3.9.1) lib/puma/thread_pool.rb:120:in `block in spawn_thread'

This happens because there is no method 'update_column' in Mongoid.

No Authorization Header on registrations#update

A JWT is meant to hold information about the user. So when a a user is updated via Devise registrations#update, the token is likely to fall into an invalid state on the client. Hence it seems expected to return an updated token when the registration is updated. Otherwise the user would need to log in after the registration is updated, which is not a natural user experience.

Does this make sense, and is it something devise-jwt can support? If not, is there an idiomatic way to handle this?

JWT::VerificationError - Signature verification raised

Hi! I'm setting up an API that used devise and devise-jwt for authentication and I followed up all the steps in order to make it work. However, whenever I tried to sign in within my BackEnd i' receive a 401 response.

So I dug up on the code and found that within warden.authenticate! was raising an error and being rescued by:

      def authenticate!
        user = UserDecoder.new.call(token, scope)
        success!(user)
      rescue JWT::DecodeError
        fail!
      end

Following that lead, I found that the error being raised was a JWT::VerificationError - Signature verification raised, however I created my secret key using rails secretas proposed in the configuration guide. Here's the line in my config/initializers/devise.rb

  config.jwt do |jwt|
    jwt.expiration_time = 3.hours
    jwt.secret = ENV.fetch('DEVISE_JWT_SECRET_KEY') { 'db70e50ec08feabaa1f05c65dadfc8057fb774052ff8fe7bc90b6d4001bfb681bbd6c26fbf06b77f39078270093948a5fd9ea09fac495fc9f7aab43241836dcf' }
  end

I even tried running it with the ENV variable forced and just plain value in the config just to test if it was something wrong with fetch method from ENV but it still doesn't work, any help?

Use `where` instead of `find` for `self.jwt_find_for_authentication`

I noticed that in the event the JWT's user is deleted, the find method in jwt_authenticatable.rb is causing my REST API to give 404 Not Found errors (bubbled upwards from an ActiveRecord::RecordNotFound exception). Would a where(id: sub).first scope be more appropriate? I'm not sure how you would integrate this with JWT subject fields.

Anyhow, I believe that a change is necessary here because Devise's own find_for_database_authentication doesn't throw exceptions and returns nil for not-found emails.

White list strategy

Hello, how about implement whitelist strategy? Where User generate many tokens(I propose jtis) and can authorize only with this tokens

Invalidation Advice (OR) pruning the blacklists table

Excuse me for posting here what would better go on Stack Overflow, but I fear I won't get enough knowledgable eyes on it there.

I'm currently using the self revocation strategy. With this strategy, a user signed into multiple devices is signed out from all of them when they request signout from any individual device. (Signing out from web will disconnect them on mobile, etc,...) This makes sense, given the definition of the self strategy as I understand it. I assume that blacklist strategy might be more appropriate for my use case, but at that point, what exists (if anything) to keep that table from growing forever? Schedule a job to purge rows that have already expired, since they'd be no good anyway? Anything better?

Appreciate your advice.

Unable to dispatch jwt token on sign_in

I am trying to create a switch_to_company functionality for my admin users(different model than users). But I am unable to dispatch jwt token on demand even after adding the required paths like so:

jwt.dispatch_requests = [
      ['POST', %r{^/sign-in/api$}],
      ['POST', %r{^users/sign_in/api$}],
      ['POST', %r{^/user_agencies/sign_in$}],
      ['POST', %r{^/admins/company/switch$}],
      ['POST', %r{^/admins/sign_in$}]
    ]

with routes:

  devise_for :users, controllers: { sessions: "sessions" }
  devise_scope :user do
    post '/admins/company/switch/:company_id', to: 'admins#switch_to_company'
    get "/sessions/new.user", to: 'sessions#new'
    post '/sign-in/api', to: 'sessions#create', get_props: true
    get "/auth/user_google_signin/callback", to: 'callbacks#user_google_signin'
  end

*I know the action is misplaced...

def switch_to_company
   @company = Company.find(params[:company_id])
   cookies.delete(:account)
   sign_out(current_user) if user_signed_in?
   sign_in(@company.users.first)
   previous_url = session[:previous_url] || ''
   previous_url = nil if previous_url.start_with?("/assets")
   respond_to do |format|
     format.html { redirect_to previous_url || dashboard_path }
     format.json { render json: @company.users.first }
   end
 end

But still can't dispatch a jwt token, is there a method I can fire to dispatch tokens on demand?

Remove default JWT payload attributes

I need to create an API that's going to use JWT authentication, but the jwt tokens should never expire.

Currently, after a successful sign_in or sign_up, the response contains the Authorization header that has the jwt, and after decoding it, the payload contains the exp attribute, which will cause the client token to become useless after a while.

How can I reject the exp attribute from the JWT payload?

I tried it like so in my user model:

def jwt_payload
  super.except!('exp')
end

and like so:

def jwt_payload
  {
    jti: jti
  }
end

I only need the jti inside the payload.

JTI issued at registration but not revoked at logout & no new token issued at login

As title says, when I register an account it issues a JTI, but it doesn't revoke it upon logout and doesn't issue a new one upon login. I don't really have any idea what could be going wrong or how to debug it.

User Model --

class User < ApplicationRecord
    include Devise::JWT::RevocationStrategies::JTIMatcher
    devise :database_authenticatable, :registerable, :omniauthable,
         :confirmable, :recoverable, :trackable, :validatable,
         :jwt_authenticatable, jwt_revocation_strategy: self
end

Devise Initializer --

config.jwt do |jwt|
    jwt.secret = Rails.application.secrets.devise_jwt_secret_key
    jwt.expiration_time = 3600
    jwt.revocation_requests = [
        ['DELETE', %r{^/user/sign_out$}],
        ['GET', %r{^/user/sign_out$}],

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

And when I inspect my network requests, there is no Authorization header, but there are session cookies.

Any help would be appreciated.

Revocation Requests

I am using a custom path for devise sessions destroy, at /api/logout. I am also overriding the sessions controller destroy method.

After logging out the user, I am still able to use the old jwt token. This leads me to believe that the revocation isn't being run on destroy.

I also added the path in the devise config file

    jwt.revocation_requests = [
      ['DELETE', %r{^/api/logout$}],
    ]

The sessions controller looks like this (I had to remove the respond_to_on_destroy given that I'm using Rails 5).

class SessionsController < Devise::SessionsController  
  skip_before_filter :verify_signed_out_user
  respond_to :json

  # DELETE /resource/sign_out
  def destroy
    signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
    yield if block_given?
    render head: :ok
  end

end

JWT acronym inflection not supported

Adding JWT acronym to the inflector config/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'JWT'
end

Generates the following exception:

NameError: uninitialized constant Devise::Models::JWTAuthenticatable

Changing jwt payload

Is there a way to change the payload in the controller instead of the model? I'm currently using devise with rolify and devise-jwt and I'd like to add my roles to the payload.

As the current_user object doesn't exist in the model as it shouldn't I can't inject the roles in the jwt_payload method in my User model.

Thanks in advance

JWT not returning on sign up or sign in

Hello there. First, thanks for the great gem that follows JWT standard!

However, I'm having issue where I'm not getting any JWT headers back in response / header. I have tried v3 as well as GitHub v4. The followings are my configs.

Sorry for long post, I don't know which part is causing issue.

Environment
Rails 5.2.0.alpha / API & Edge mode

2017-08-21_07 12 46_valkyrie_ss

2017-08-21_07 13 37_valkyrie_ss

2017-08-21_07 13 08_valkyrie_ss

controllers/application_controller.rb

class ApplicationController < ActionController::API
  respond_to :json
  include ActionController::Serialization
end

initializers/devise.rb

  config.jwt do |jwt|
    # jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
    jwt.secret = "d8f531be58ad478cc79d072d6c4ea777a0c6ca5fbd9a2194b6fbfcea5f4f122017092a593f31576a0c27d9478f1bd74d336945481d22c7f9af36fd2fa6061c3d"
    jwt.dispatch_requests = [
      ['GET', %r{^/api/v1/users/sign_in.json$}],
      ['POST', %r{^/api/v1/users/sign_in.json$}],
      ['GET', %r{^/api/v1/users$}],
      ['POST', %r{^/api/v1/users$}]
    ]
    jwt.revocation_requests = [
      ['GET', %r{^/api/v1/users$}],
      ['POST', %r{^/api/v1/users$}]
    ]
    jwt.requests_formats = { user: [:json] }
  end

Extra info

models/users.rb

class User < ApplicationRecord
  devise :database_authenticatable,
         :registerable,
         :recoverable,
         :trackable, 
         :validatable,
         :jwt_authenticatable,
         jwt_revocation_strategy: JWTBlacklist
end

models/jwt_blacklist.rb

class JWTBlacklist < ApplicationRecord
  include Devise::JWT::RevocationStrategies::Blacklist

  self.table_name = 'jwt_blacklists'
end

config/routes.rb (I have tried default controllers, did this explicitly for byebug)

Rails.application.routes.draw do
  mount Rswag::Ui::Engine => '/api-docs'
  mount Rswag::Api::Engine => '/api-docs'
  
  devise_for  :users,
              :controllers => {
                sessions: 'api/v1/users/sessions',
                registrations: 'api/v1/users/registrations',
                passwords: 'api/v1/users/passwords'
              },
              :path => '/api/v1/users',
              defaults: { format: :json }

  api_version(:module => "Api::V1", :path => {:value => "api/v1"}) do
    
    resources :clubs
    resources :events
    resource :galleries do
      resources :images, :only => [:create, :destroy]
    end
                
  end
  
end

$ rails routes

โžœ rails routes
                  Prefix Verb   URI Pattern                            Controller#Action
                rswag_ui        /api-docs                              Rswag::Ui::Engine
               rswag_api        /api-docs                              Rswag::Api::Engine
        new_user_session GET    /api/v1/users/sign_in(.:format)        api/v1/users/sessions#new {:format=>:json}
            user_session POST   /api/v1/users/sign_in(.:format)        api/v1/users/sessions#create {:format=>:json}
    destroy_user_session DELETE /api/v1/users/sign_out(.:format)       api/v1/users/sessions#destroy {:format=>:json}
       new_user_password GET    /api/v1/users/password/new(.:format)   api/v1/users/passwords#new {:format=>:json}
      edit_user_password GET    /api/v1/users/password/edit(.:format)  api/v1/users/passwords#edit {:format=>:json}
           user_password PATCH  /api/v1/users/password(.:format)       api/v1/users/passwords#update {:format=>:json}
                         PUT    /api/v1/users/password(.:format)       api/v1/users/passwords#update {:format=>:json}
                         POST   /api/v1/users/password(.:format)       api/v1/users/passwords#create {:format=>:json}
cancel_user_registration GET    /api/v1/users/cancel(.:format)         api/v1/users/registrations#cancel {:format=>:json}
   new_user_registration GET    /api/v1/users/sign_up(.:format)        api/v1/users/registrations#new {:format=>:json}
  edit_user_registration GET    /api/v1/users/edit(.:format)           api/v1/users/registrations#edit {:format=>:json}
       user_registration PATCH  /api/v1/users(.:format)                api/v1/users/registrations#update {:format=>:json}
                         PUT    /api/v1/users(.:format)                api/v1/users/registrations#update {:format=>:json}
                         DELETE /api/v1/users(.:format)                api/v1/users/registrations#destroy {:format=>:json}
                         POST   /api/v1/users(.:format)                api/v1/users/registrations#create {:format=>:json}
            api_v1_clubs GET    /api/v1/clubs(.:format)                api/v1/clubs#index
                         POST   /api/v1/clubs(.:format)                api/v1/clubs#create
             api_v1_club GET    /api/v1/clubs/:id(.:format)            api/v1/clubs#show
                         PATCH  /api/v1/clubs/:id(.:format)            api/v1/clubs#update
                         PUT    /api/v1/clubs/:id(.:format)            api/v1/clubs#update
                         DELETE /api/v1/clubs/:id(.:format)            api/v1/clubs#destroy
           api_v1_events GET    /api/v1/events(.:format)               api/v1/events#index
                         POST   /api/v1/events(.:format)               api/v1/events#create
            api_v1_event GET    /api/v1/events/:id(.:format)           api/v1/events#show
                         PATCH  /api/v1/events/:id(.:format)           api/v1/events#update
                         PUT    /api/v1/events/:id(.:format)           api/v1/events#update
                         DELETE /api/v1/events/:id(.:format)           api/v1/events#destroy
 api_v1_galleries_images POST   /api/v1/galleries/images(.:format)     api/v1/images#create
  api_v1_galleries_image DELETE /api/v1/galleries/images/:id(.:format) api/v1/images#destroy
        api_v1_galleries GET    /api/v1/galleries(.:format)            api/v1/galleries#show
                         PATCH  /api/v1/galleries(.:format)            api/v1/galleries#update
                         PUT    /api/v1/galleries(.:format)            api/v1/galleries#update
                         DELETE /api/v1/galleries(.:format)            api/v1/galleries#destroy
                         POST   /api/v1/galleries(.:format)            api/v1/galleries#create

Routes for Rswag::Ui::Engine:
  root GET  /           rswag/ui/home#index

Routes for Rswag::Api::Engine:

config/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  
  allow do
    origins "localhost:3000", "127.0.0.1:3000", "*"
    resource "*",
      :headers => :any,
      :expose  => [
        "X-Requested-With",
        "Content-Type",
        "Authorization",
        "Accept",
        "Client-Security-Token",
        "Accept-Encoding",
        "iat",
        "exp",
        "jti"
      ],
      :methods => [:get, :post, :options, :delete, :put]
  end

schema.rb

  create_table "jwt_blacklists", force: :cascade do |t|
    t.string "jti", null: false
    t.datetime "exp", null: false
    t.index ["jti"], name: "index_jwt_blacklists_on_jti"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet "current_sign_in_ip"
    t.inet "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

sign_in_count increments on every request

Using trackable for devise. When client hits API protected by authenticate_user!, sign_in_count increments even if user is signed in
Log:

Started GET "/v1/cities" for 127.0.0.1 at 2017-05-10 18:28:40 +0300
Processing by V1::CitiesController#index as JSON
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 5], ["LIMIT", 1]]
   (0.1ms)  BEGIN
  SQL (0.3ms)  UPDATE "users" SET "current_sign_in_at" = $1, "last_sign_in_at" = $2, "sign_in_count" = $3, "updated_at" = $4 WHERE "users"."id" = $5  [["current_sign_in_at", "2017-05-10 15:28:40.552873"], ["last_sign_in_at", "2017-05-10 15:28:27.810227"], ["sign_in_count", 127], ["updated_at", "2017-05-10 15:28:40.553390"], ["id", 5]]
   (2.2ms)  COMMIT
  City Load (0.2ms)  SELECT "cities".* FROM "cities"
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (0.05ms)
Completed 200 OK in 7ms (Views: 0.7ms | ActiveRecord: 2.9ms)

Not sure if it is a devise-jwt issue

No implicit conversion of nil into String

When I made a POST request to /users/sign_in this error is raised

{
    "status": 500,
    "error": "Internal Server Error",
    "exception": "#<TypeError: no implicit conversion of nil into String>",
    "traces": {
        "Application Trace": [],
        "Framework Trace": [
            {
                "id": 0,
                "trace": "jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `digest'"
            },
            {
                "id": 1,
                "trace": "jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `sign'"
            },
            {
                "id": 2,
                "trace": "jwt (2.1.0) lib/jwt/signature.rb:35:in `sign'"
            },
            {
                "id": 3,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:39:in `encoded_signature'"
            },
            {
                "id": 4,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:47:in `encode_segments'"
            },
            {
                "id": 5,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:20:in `initialize'"
            },
            {
                "id": 6,
                "trace": "jwt (2.1.0) lib/jwt.rb:21:in `new'"
            },
            {
                "id": 7,
                "trace": "jwt (2.1.0) lib/jwt.rb:21:in `encode'"
            },
            {
                "id": 8,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/token_encoder.rb:21:in `call'"
            },
            {
                "id": 9,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/user_encoder.rb:25:in `call'"
            },
            {
                "id": 10,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:27:in `prepare_token'"
            },
            {
                "id": 11,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:18:in `after_set_user'"
            },
            {
                "id": 12,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:54:in `block in <top (required)>'"
            },
            {
                "id": 13,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:15:in `block in _run_callbacks'"
            },
            {
                "id": 14,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:10:in `each'"
            },
            {
                "id": 15,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:10:in `_run_callbacks'"
            },
            {
                "id": 16,
                "trace": "warden (1.2.7) lib/warden/manager.rb:52:in `_run_callbacks'"
            },
            {
                "id": 17,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:180:in `set_user'"
            },
            {
                "id": 18,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:328:in `_perform_authentication'"
            },
            {
                "id": 19,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:128:in `authenticate!'"
            },
            {
                "id": 20,
                "trace": "devise (4.3.0) app/controllers/devise/sessions_controller.rb:17:in `create'"
            },
            {
                "id": 21,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'"
            },
            {
                "id": 22,
                "trace": "actionpack (5.1.4) lib/abstract_controller/base.rb:186:in `process_action'"
            },
            {
                "id": 23,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/rendering.rb:30:in `process_action'"
            },
            {
                "id": 24,
                "trace": "actionpack (5.1.4) lib/abstract_controller/callbacks.rb:20:in `block in process_action'"
            },
            {
                "id": 25,
                "trace": "activesupport (5.1.4) lib/active_support/callbacks.rb:131:in `run_callbacks'"
            },
            {
                "id": 26,
                "trace": "actionpack (5.1.4) lib/abstract_controller/callbacks.rb:19:in `process_action'"
            },
            {
                "id": 27,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/rescue.rb:20:in `process_action'"
            },
            {
                "id": 28,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'"
            },
            {
                "id": 29,
                "trace": "activesupport (5.1.4) lib/active_support/notifications.rb:166:in `block in instrument'"
            },
            {
                "id": 30,
                "trace": "activesupport (5.1.4) lib/active_support/notifications/instrumenter.rb:21:in `instrument'"
            },
            {
                "id": 31,
                "trace": "activesupport (5.1.4) lib/active_support/notifications.rb:166:in `instrument'"
            },
            {
                "id": 32,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:30:in `process_action'"
            },
            {
                "id": 33,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/params_wrapper.rb:252:in `process_action'"
            },
            {
                "id": 34,
                "trace": "activerecord (5.1.4) lib/active_record/railties/controller_runtime.rb:22:in `process_action'"
            },
            {
                "id": 35,
                "trace": "actionpack (5.1.4) lib/abstract_controller/base.rb:124:in `process'"
            },
            {
                "id": 36,
                "trace": "actionpack (5.1.4) lib/action_controller/metal.rb:189:in `dispatch'"
            },
            {
                "id": 37,
                "trace": "actionpack (5.1.4) lib/action_controller/metal.rb:253:in `dispatch'"
            },
            {
                "id": 38,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'"
            },
            {
                "id": 39,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:31:in `serve'"
            },
            {
                "id": 40,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/mapper.rb:16:in `block in <class:Constraints>'"
            },
            {
                "id": 41,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/mapper.rb:46:in `serve'"
            },
            {
                "id": 42,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:50:in `block in serve'"
            },
            {
                "id": 43,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `each'"
            },
            {
                "id": 44,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `serve'"
            },
            {
                "id": 45,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:834:in `call'"
            },
            {
                "id": 46,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware/token_dispatcher.rb:20:in `call'"
            },
            {
                "id": 47,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware/revocation_manager.rb:20:in `call'"
            },
            {
                "id": 48,
                "trace": "rack (2.0.3) lib/rack/builder.rb:153:in `call'"
            },
            {
                "id": 49,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware.rb:23:in `call'"
            },
            {
                "id": 50,
                "trace": "warden (1.2.7) lib/warden/manager.rb:36:in `block in call'"
            },
            {
                "id": 51,
                "trace": "warden (1.2.7) lib/warden/manager.rb:35:in `catch'"
            },
            {
                "id": 52,
                "trace": "warden (1.2.7) lib/warden/manager.rb:35:in `call'"
            },
            {
                "id": 53,
                "trace": "rack (2.0.3) lib/rack/etag.rb:25:in `call'"
            },
            {
                "id": 54,
                "trace": "rack (2.0.3) lib/rack/conditional_get.rb:38:in `call'"
            },
            {
                "id": 55,
                "trace": "rack (2.0.3) lib/rack/head.rb:12:in `call'"
            },
            {
                "id": 56,
                "trace": "activerecord (5.1.4) lib/active_record/migration.rb:556:in `call'"
            },
            {
                "id": 57,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'"
            },
            {
                "id": 58,
                "trace": "activesupport (5.1.4) lib/active_support/callbacks.rb:97:in `run_callbacks'"
            },
            {
                "id": 59,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:24:in `call'"
            },
            {
                "id": 60,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'"
            },
            {
                "id": 61,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'"
            },
            {
                "id": 62,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'"
            },
            {
                "id": 63,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'"
            },
            {
                "id": 64,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'"
            },
            {
                "id": 65,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'"
            },
            {
                "id": 66,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'"
            },
            {
                "id": 67,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'"
            },
            {
                "id": 68,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'"
            },
            {
                "id": 69,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'"
            },
            {
                "id": 70,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'"
            },
            {
                "id": 71,
                "trace": "rack (2.0.3) lib/rack/runtime.rb:22:in `call'"
            },
            {
                "id": 72,
                "trace": "activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'"
            },
            {
                "id": 73,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'"
            },
            {
                "id": 74,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/static.rb:125:in `call'"
            },
            {
                "id": 75,
                "trace": "rack (2.0.3) lib/rack/sendfile.rb:111:in `call'"
            },
            {
                "id": 76,
                "trace": "rack-cors (1.0.2) lib/rack/cors.rb:97:in `call'"
            },
            {
                "id": 77,
                "trace": "railties (5.1.4) lib/rails/engine.rb:522:in `call'"
            },
            {
                "id": 78,
                "trace": "puma (3.11.0) lib/puma/configuration.rb:225:in `call'"
            },
            {
                "id": 79,
                "trace": "puma (3.11.0) lib/puma/server.rb:624:in `handle_request'"
            },
            {
                "id": 80,
                "trace": "puma (3.11.0) lib/puma/server.rb:438:in `process_client'"
            },
            {
                "id": 81,
                "trace": "puma (3.11.0) lib/puma/server.rb:302:in `block in run'"
            },
            {
                "id": 82,
                "trace": "puma (3.11.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'"
            }
        ],
        "Full Trace": [
            {
                "id": 0,
                "trace": "jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `digest'"
            },
            {
                "id": 1,
                "trace": "jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `sign'"
            },
            {
                "id": 2,
                "trace": "jwt (2.1.0) lib/jwt/signature.rb:35:in `sign'"
            },
            {
                "id": 3,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:39:in `encoded_signature'"
            },
            {
                "id": 4,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:47:in `encode_segments'"
            },
            {
                "id": 5,
                "trace": "jwt (2.1.0) lib/jwt/encode.rb:20:in `initialize'"
            },
            {
                "id": 6,
                "trace": "jwt (2.1.0) lib/jwt.rb:21:in `new'"
            },
            {
                "id": 7,
                "trace": "jwt (2.1.0) lib/jwt.rb:21:in `encode'"
            },
            {
                "id": 8,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/token_encoder.rb:21:in `call'"
            },
            {
                "id": 9,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/user_encoder.rb:25:in `call'"
            },
            {
                "id": 10,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:27:in `prepare_token'"
            },
            {
                "id": 11,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:18:in `after_set_user'"
            },
            {
                "id": 12,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/hooks.rb:54:in `block in <top (required)>'"
            },
            {
                "id": 13,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:15:in `block in _run_callbacks'"
            },
            {
                "id": 14,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:10:in `each'"
            },
            {
                "id": 15,
                "trace": "warden (1.2.7) lib/warden/hooks.rb:10:in `_run_callbacks'"
            },
            {
                "id": 16,
                "trace": "warden (1.2.7) lib/warden/manager.rb:52:in `_run_callbacks'"
            },
            {
                "id": 17,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:180:in `set_user'"
            },
            {
                "id": 18,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:328:in `_perform_authentication'"
            },
            {
                "id": 19,
                "trace": "warden (1.2.7) lib/warden/proxy.rb:128:in `authenticate!'"
            },
            {
                "id": 20,
                "trace": "devise (4.3.0) app/controllers/devise/sessions_controller.rb:17:in `create'"
            },
            {
                "id": 21,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'"
            },
            {
                "id": 22,
                "trace": "actionpack (5.1.4) lib/abstract_controller/base.rb:186:in `process_action'"
            },
            {
                "id": 23,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/rendering.rb:30:in `process_action'"
            },
            {
                "id": 24,
                "trace": "actionpack (5.1.4) lib/abstract_controller/callbacks.rb:20:in `block in process_action'"
            },
            {
                "id": 25,
                "trace": "activesupport (5.1.4) lib/active_support/callbacks.rb:131:in `run_callbacks'"
            },
            {
                "id": 26,
                "trace": "actionpack (5.1.4) lib/abstract_controller/callbacks.rb:19:in `process_action'"
            },
            {
                "id": 27,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/rescue.rb:20:in `process_action'"
            },
            {
                "id": 28,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'"
            },
            {
                "id": 29,
                "trace": "activesupport (5.1.4) lib/active_support/notifications.rb:166:in `block in instrument'"
            },
            {
                "id": 30,
                "trace": "activesupport (5.1.4) lib/active_support/notifications/instrumenter.rb:21:in `instrument'"
            },
            {
                "id": 31,
                "trace": "activesupport (5.1.4) lib/active_support/notifications.rb:166:in `instrument'"
            },
            {
                "id": 32,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:30:in `process_action'"
            },
            {
                "id": 33,
                "trace": "actionpack (5.1.4) lib/action_controller/metal/params_wrapper.rb:252:in `process_action'"
            },
            {
                "id": 34,
                "trace": "activerecord (5.1.4) lib/active_record/railties/controller_runtime.rb:22:in `process_action'"
            },
            {
                "id": 35,
                "trace": "actionpack (5.1.4) lib/abstract_controller/base.rb:124:in `process'"
            },
            {
                "id": 36,
                "trace": "actionpack (5.1.4) lib/action_controller/metal.rb:189:in `dispatch'"
            },
            {
                "id": 37,
                "trace": "actionpack (5.1.4) lib/action_controller/metal.rb:253:in `dispatch'"
            },
            {
                "id": 38,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'"
            },
            {
                "id": 39,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:31:in `serve'"
            },
            {
                "id": 40,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/mapper.rb:16:in `block in <class:Constraints>'"
            },
            {
                "id": 41,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/mapper.rb:46:in `serve'"
            },
            {
                "id": 42,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:50:in `block in serve'"
            },
            {
                "id": 43,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `each'"
            },
            {
                "id": 44,
                "trace": "actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `serve'"
            },
            {
                "id": 45,
                "trace": "actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:834:in `call'"
            },
            {
                "id": 46,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware/token_dispatcher.rb:20:in `call'"
            },
            {
                "id": 47,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware/revocation_manager.rb:20:in `call'"
            },
            {
                "id": 48,
                "trace": "rack (2.0.3) lib/rack/builder.rb:153:in `call'"
            },
            {
                "id": 49,
                "trace": "warden-jwt_auth (0.2.0) lib/warden/jwt_auth/middleware.rb:23:in `call'"
            },
            {
                "id": 50,
                "trace": "warden (1.2.7) lib/warden/manager.rb:36:in `block in call'"
            },
            {
                "id": 51,
                "trace": "warden (1.2.7) lib/warden/manager.rb:35:in `catch'"
            },
            {
                "id": 52,
                "trace": "warden (1.2.7) lib/warden/manager.rb:35:in `call'"
            },
            {
                "id": 53,
                "trace": "rack (2.0.3) lib/rack/etag.rb:25:in `call'"
            },
            {
                "id": 54,
                "trace": "rack (2.0.3) lib/rack/conditional_get.rb:38:in `call'"
            },
            {
                "id": 55,
                "trace": "rack (2.0.3) lib/rack/head.rb:12:in `call'"
            },
            {
                "id": 56,
                "trace": "activerecord (5.1.4) lib/active_record/migration.rb:556:in `call'"
            },
            {
                "id": 57,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'"
            },
            {
                "id": 58,
                "trace": "activesupport (5.1.4) lib/active_support/callbacks.rb:97:in `run_callbacks'"
            },
            {
                "id": 59,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:24:in `call'"
            },
            {
                "id": 60,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'"
            },
            {
                "id": 61,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'"
            },
            {
                "id": 62,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'"
            },
            {
                "id": 63,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'"
            },
            {
                "id": 64,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'"
            },
            {
                "id": 65,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'"
            },
            {
                "id": 66,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'"
            },
            {
                "id": 67,
                "trace": "activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'"
            },
            {
                "id": 68,
                "trace": "railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'"
            },
            {
                "id": 69,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'"
            },
            {
                "id": 70,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'"
            },
            {
                "id": 71,
                "trace": "rack (2.0.3) lib/rack/runtime.rb:22:in `call'"
            },
            {
                "id": 72,
                "trace": "activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'"
            },
            {
                "id": 73,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'"
            },
            {
                "id": 74,
                "trace": "actionpack (5.1.4) lib/action_dispatch/middleware/static.rb:125:in `call'"
            },
            {
                "id": 75,
                "trace": "rack (2.0.3) lib/rack/sendfile.rb:111:in `call'"
            },
            {
                "id": 76,
                "trace": "rack-cors (1.0.2) lib/rack/cors.rb:97:in `call'"
            },
            {
                "id": 77,
                "trace": "railties (5.1.4) lib/rails/engine.rb:522:in `call'"
            },
            {
                "id": 78,
                "trace": "puma (3.11.0) lib/puma/configuration.rb:225:in `call'"
            },
            {
                "id": 79,
                "trace": "puma (3.11.0) lib/puma/server.rb:624:in `handle_request'"
            },
            {
                "id": 80,
                "trace": "puma (3.11.0) lib/puma/server.rb:438:in `process_client'"
            },
            {
                "id": 81,
                "trace": "puma (3.11.0) lib/puma/server.rb:302:in `block in run'"
            },
            {
                "id": 82,
                "trace": "puma (3.11.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'"
            }
        ]
    }
}

Using devise-jwt with custom sessions controller

Hi,
I am trying to use this gem with a custom sessions controller based on Devise but a can't get the token after login.
using this code

class V1::SessionsController < ApplicationController

    def create
      user = User.where(username: params[:user][:username]).first
      if user&.valid_password?(params[:user][:password])
        render json: user.as_json, status: :created
      else
        head(:unauthorized)
      end
    end
end

with this options

  jwt.dispatch_requests = [['POST', %r{^/v1/users/sign_in$}]]
  jwt.request_formats = { user: [:json] }

and this routes

Rails.application.routes.draw do
  namespace :v1, defaults: { format: :json } do
    devise_for :users
  end
end

I did get this working when using the next routes, which calls the devise sessions controller

Rails.application.routes.draw do
  scope :v1, defaults: { format: :json } do
    devise_for :users
  end
end

Change default sub

How to change value for sub? By default, it is using id. I would like to use other uuid instead. How can I do so? I tried doing

def jwt_payload
  super.merge(sub: self.uuid)
end

Authorization header is not being returned

I have set up devise and devise-jwt on a new rails project just to test this out.
When I POST to /users/sign_in
the response headers does not include an Authorization header, but the post request succeeds

The result I get shown in postman:
screen shot 2017-08-24 at 2 50 39 pm

I am using the null revocation strategy
models/user.rb
screen shot 2017-08-24 at 3 05 10 pm

devise.rb
screen shot 2017-08-24 at 3 07 23 pm

application.rb
screen shot 2017-08-24 at 3 11 23 pm

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.