Library / SDK
waiting-for-dev/devise-jwt avatar
waiting-for-dev/devise-jwt

devise-jwt swaps Devise's session cookie for a token that the server can revoke

JWT token authentication with devise and rails

1,292 stars130 forksRubyMIT

At a glance

What is it?
devise-jwt is a Ruby gem that adds JWT authentication to a Devise and Rails application, with mandatory token expiration and server side revocation strategies. It is aimed at teams moving a Rails backend to a stateless API while keeping Devise as the identity layer.
Who is it for?
devise-jwt fits a Rails team that already uses Devise and now needs bearer token authentication for a mobile or API client, and it fits that team better than a hand written JWT middleware because revocation and expiration are built in rather than bolted on.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 123 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A cookie replacement for the case where a cookie cannot be used

The framing in the project documentation is deliberately narrow. devise-jwt is described as a Devise extension that uses JWT tokens for user authentication, and it follows what the project calls a secure by default principle. The most important sentence for scoping is that this gem is just a replacement for cookies when these cannot be used. It is not a proposal to remove Devise from a Rails application, and it is not a general purpose JWT library. Instead it takes the session handling that Devise already provides and swaps the transport for a token, keeping the parts that Rails developers already depend on, such as the create session request, the user model and the destroy session request. That makes it a plausible fit for a Rails backend that needs to serve a mobile client or a separate SPA, where a session cookie either cannot be sent or should not be sent at all. It is a fit for a team that has already committed to Devise, and a poor fit for a team that has not.

The request flow ends with a token the server can pull back

The model configuration section spells out the flow for every user model you mark as JWT capable. A user authenticates through the 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 the format Bearer, and tokens are also dispatched on a successful sign up. The client then presents that token in the Authorization request header on later requests, again in the Bearer format, and the server authenticates the same user from it. When the client visits the Devise destroy session request, the token is revoked. The project is explicit that this differs from other JWT authentication libraries, because it is expected that tokens will be revoked by the server. That single expectation is the whole design, and it is what the revocation strategy setting exists to implement.

A thin layer over warden-jwt_auth rather than a from scratch implementation

Architecture is not hidden in this project. The documentation states that devise-jwt is just a thin layer on top of warden-jwt_auth, a separate library, configured to be used out of the box with Devise and Rails. Anyone evaluating the gem is really evaluating two things: the Devise specific glue, and the underlying Warden strategy. That has a practical consequence for maintenance and for reading the code. There is less of devise-jwt to audit, and the behaviour that matters most, the token lifecycle, lives in a separate repository with its own release cadence. The repository layout supports this reading: the top level contains lib, spec, a Gemfile, a Rakefile, a devise-jwt.gemspec, an .rspec file and a .rubocop.yml, with a CHANGELOG.md and a CODE_OF_CONDUCT.md alongside them. The project also publishes a series of blog posts on its own site covering JWT revocation, revocation strategies, secure JWT usage in general, and a secure JWT authentication implementation for Rack and Rails, which is where the design reasoning is written down.

Installing the gem and pointing a user model at the denylist strategy

Installation is a Gemfile entry and a bundle. You can also install it directly with the gem command. After that, Devise itself has to be configured to work in an API application, and the documentation sends you to the wiki page on configuring Devise for APIs rather than restating those steps here.

ruby
gem 'devise-jwt'
bash
$ bundle

The user model then needs the jwt_authenticatable module and a revocation strategy.

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

The revocation strategies are available under the Devise::JWT::RevocationStrategies namespace. The name you write in the model above refers to that constant. If you need to add something to the JWT payload, the documentation points to defining it in the same way the rest of the module configuration is customised.

The signing secret defaults to HS256, and an asymmetric key needs a second secret

Token signing is configured in the Devise initializer under a jwt block, and the default algorithm is HS256. A symmetric secret is enough for that case, and the README also explains how to move to an asymmetric algorithm such as RS256, which needs a separate decoding secret because the verifying key differs from the signing key.

ruby
Devise.setup do |config|
  # ...
  config.jwt do |jwt|
    jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
  end
end
ruby
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

If you use Encrypted Credentials on Rails 5.2 or newer, the key can live in config/credentials.yml.enc under the name devise_jwt_secret_key, and the editor is opened with bin/rails credentials:edit, which may require you to set $EDITOR. The project encourages using a secret different from the application secret_key_base, on the grounds that other components may already be using it and a shared secret widens the impact of a vulnerability in any one of them. New secrets are generated with rails secret, and the documentation advises against pushing secrets to a remote repository, preferring an environment variable.

Denylist and Allowlist replaced Blacklist and Whitelist in v0.7.0

The upgrade notes document a rename rather than a behavioural change, and it is the kind of change that breaks a deployment if you miss it. Since v0.7.0 the Blacklist revocation strategy has been renamed to Denylist, and Whitelist to Allowlist. For the denylist strategy you only need to update the include line in your revocation strategy model.

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

The allowlist case is heavier, because you have to rename the 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.

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

The table rename is the part with data migration implications, and the escape hatch the README offers is a model configuration that keeps the old table name. Recent tagged releases are v0.11.0 on 2026-01-14 and v0.10.0 on 2022-09-16, and the last push was on 2026-06-22.

Mandatory expiration is the design constraint you cannot configure away

The hard boundary of this gem is written into its own documentation. As with cookies, a devise-jwt token will mandatorily have an expiration time. The project then draws a line for you: if you need users to never sign out, you are better off with a solution using refresh tokens, like some implementation of OAuth2. That is a direct instruction not to try to bend the expiration into a permanent session. It also frames the gem's value proposition honestly, since the alternative to a cookie is not always a token, and in the long lived session case it is a refresh token flow that this library does not provide. The related configuration detail is worth noting for anyone using paths with a format segment such as .json: the request_formats configuration option exists so that such paths are handled properly. The documentation does not describe refresh token support, token rotation, or a blacklisting cache with a time to live.

Editorial conclusion

devise-jwt fits a Rails team that already uses Devise and now needs bearer token authentication for a mobile or API client, and it fits that team better than a hand written JWT middleware because revocation and expiration are built in rather than bolted on. It is the wrong tool when your users must never sign out, since the token always expires and the project points you to a refresh token scheme such as an OAuth2 implementation for that case, and it is also the wrong tool if you want to drop Devise entirely. Before adding it, confirm your Rails and Devise versions against the gem's own documentation, decide between the Denylist and Allowlist revocation strategies, and generate a separate secret with rails secret rather than reusing secret_key_base. The gem is MIT licensed and the last push was on 2026-06-22.

Frequently asked questions

How do I install devise-jwt in a Rails application?

Add gem 'devise-jwt' to the application's Gemfile and run bundle, or install it directly with gem install devise-jwt. Devise also has to be configured to work in an API application, which the project covers on a separate wiki page.

Which revocation strategy should I use with devise-jwt?

The strategies are Devise::JWT::RevocationStrategies::Denylist and Devise::JWT::RevocationStrategies::Allowlist, the latter two having been renamed from Blacklist and Whitelist in v0.7.0. The model configuration names the strategy in the devise call, for example jwt_revocation_strategy: Denylist.

Can devise-jwt issue tokens that never expire?

No. A devise-jwt token will mandatorily have an expiration time, in the same way a cookie does. The documentation states that if users must never sign out, a solution using refresh tokens, such as an OAuth2 implementation, is the better choice.

How do I change the algorithm from HS256 to RS256?

Set jwt.algorithm and, because an asymmetric algorithm needs a different decoding secret, set jwt.decoding_secret to the public key while jwt.secret holds the private key. The secret itself can come from ENV or from Encrypted Credentials, where the key is stored as devise_jwt_secret_key.

Does devise-jwt replace Devise?

No. It is a Devise extension and a thin layer on top of warden-jwt_auth, configured to work out of the box with Devise and Rails. It replaces the cookie with a token while keeping the Devise create session and destroy session requests.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. waiting-for-dev/devise-jwt on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/waiting-for-dev-devise-jwt.svg)](https://hysenlabs.com/projects/waiting-for-dev-devise-jwt)

Was this useful?