Sorcery::Jwt
Jwt extension for the Sorcery authentication library, for API-only Rails apps.
NOTE: Sorcery v1 is being developed and JWT is planned as a core plugin. See Sorcery/sorcery-rework#9. This gem targets the current (0.16–0.18) sorcery line.
Installation
Add this line to your application's Gemfile:
gem "sorcery-jwt"And then execute:
$ bundle
Usage
First, include the :jwt submodule in your list of configured Sorcery submodules:
Rails.application.config.sorcery.submodules = [:jwt, ...]Next, in the Sorcery user_config, set the secret and algorithm used to sign
tokens, the token lifetime in seconds, and (optionally) clock-skew tolerance:
Rails.application.config.sorcery.configure do |config|
# ...
config.user_config do |user|
user.jwt_secret = Rails.application.secrets.secret_key_base
user.jwt_algorithm = "HS256" # default; see jwt/ruby-jwt for options
user.session_expiry = 60 * 60 * 24 * 7 * 2 # default: 2 weeks (seconds)
user.exp_leeway = 30 # optional; default nil = no leeway
end
endWith the submodule included, each request checks the Authorization header
for a bearer JWT. If the token is validly signed, unexpired, and its claims
match a user, current_user is set. Handling invalid tokens (401s, etc.) is
up to your application.
To log a user in and issue a token:
class SessionsController < ApplicationController
def create
token = login_and_issue_token(params[:email], params[:password])
if token
render json: { token: token }, status: :created
else
render json: { error: "invalid credentials" }, status: :unauthorized
end
end
endClaim contract
Tokens issued by login_and_issue_token carry the user's id and email,
plus the standard exp and iat claims. Authentication via
login_from_jwt requires all of the following, evaluated in one place:
- a valid signature under the configured secret and algorithm
- an unexpired
exp(withinexp_leeway, if configured) -
idandemailclaims are present - the claims match an existing user
A validly-signed token that fails any check authenticates nobody — notably, a token missing its identity claims can never fall through to an unfiltered lookup.
Token issuance API
You can also issue tokens directly from the user model:
User.issue_token(id: user.id, email: user.email) # => signed JWT string
User.decode_token(token) # => [payload, header]; raises JWT::DecodeError
User.token_valid?(token) # => boolean; never raisesSecurity notes
-
No revocation is built in. Tokens are valid until
exp. If you need logout, invalidation on password change, or single-session enforcement, see JWT revocation strategies — any denylist/token-version approach can be layered on top by your app. - The algorithm is pinned on decode:
alg: noneand key-confusion attacks are rejected. - The signing secret should be at least as strong as
secret_key_baserotated from your credentials store.
Upgrading from 0.1.x
No API changes: issue_token, decode_token, token_valid?,
login_and_issue_token, and the login_from_jwt login source all behave as
before, and tokens issued by 0.1.x remain valid — no forced re-login.
Behavior fixes in the 0.2+ line:
- tokens with no
id/emailclaims are rejected (previously authenticated the first user in the table) - the
emailclaim must match the looked-up user - failed JWT login sets
current_usertonil(sorcery convention) - the model submodule actually registers on modern Rubies (see
CHANGELOG — before 0.2, sorcery's
rescue NameErrorcould silently skip it, leavingissue_tokenundefined at runtime)
Compatibility
Tested matrix (CI): Ruby 3.2 / 3.3 / 3.4 × sorcery 0.16.5 / 0.17 / 0.18.
Contributing
Bug reports and pull requests are welcome at https://github.com/hayfever/sorcery-jwt.
License
The gem is available as open source under the terms of the MIT License.