ruby-jwt: the RFC 7519 token library behind most Ruby auth stacks
A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.
At a glance
- What is it?
- ruby-jwt is the long-standing Ruby gem for encoding and decoding JSON Web Tokens. It is small, MIT licensed, and mostly a thin layer over OpenSSL, which is both its strength and the reason its decode options matter so much.
- Who is it for?
- Adopt ruby-jwt if you are building token issuance or verification in Ruby and you want a library that stays close to OpenSSL and RFC 7518 rather than wrapping it in a framework abstraction. Do not adopt it if you expect it to manage token revocation, key rotation schedules, or session storage; it does none of that.
- 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 5 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 September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ruby-jwt actually does, and who reaches for it
The gem implements RFC 7519, the JSON Web Token standard, in Ruby. That is the whole scope: it turns a payload hash plus a key into a signed or unsigned token string, and it turns a token string back into a payload and header. It does not store tokens, revoke them, refresh them, or talk to a database. Anything about sessions, blacklists, or refresh flows is your application's problem.
That narrowness is the point. The people who need it are Ruby and Rails developers wiring up an API that hands out bearer tokens, or a service that has to verify tokens issued somewhere else. If you are on Rails with Devise, the ecosystem convention is a companion gem that plugs ruby-jwt into the Devise flow rather than calling the library directly. If you are building a plain Rack or Sinatra service, you call JWT.encode and JWT.decode yourself.
The gem is MIT licensed and the repository is not archived; the last push was on 2026-09-11, the same day as the v3.3.0 release. That release cadence matters more than any feature list, because a JWT library that stops receiving fixes is a liability, not a convenience.
Algorithms, OpenSSL, and why the decode options hash is the whole story
The README states that the gem natively supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the OpenSSL library. Concretely that means HS256/384/512, RS256/384/512, ES256/384/512 plus ES256K, and PS256/384/512. EdDSA is not in the core gem: since version 3.0 it lives in the separate jwt-eddsa gem, which is the single most important thing to know if you are upgrading from a 2.x line.
The mechanism is deliberately thin. JWT.encode serializes the header and payload, base64url encodes them, and hands the signing input to the algorithm implementation, which in most cases is an OpenSSL primitive. JWT.decode splits the token into three parts, recomputes the signature over the first two, and compares. There is no caching layer, no key registry, no automatic key fetching from a JWKS endpoint in the core API.
That design puts the security burden on the options hash. The README is blunt about this: you must specify the algorithm whenever you call JWT.decode, and it strongly recommends hard coding it rather than picking it dynamically, because a dynamic choice can let an attacker steer verification. The library supports the NONE algorithm, which is exactly what you want for tests and exactly what you never want in production, so the algorithm option is not a formality.
The extension point is JWT::JWA::SigningAlgorithm. A custom object that extends that module and implements alg plus sign for encoding, or alg plus verify for decoding, can be passed through the algorithm option. That is how you would plug in a hardware-backed signer or a non-standard algorithm without forking the gem.
Installing ruby-jwt and signing your first token
The README gives two install paths. For a one-off, install the gem directly. You should see the jwt gem resolved and installed.
gem install jwtInside an application, the README says to add the gem to your Gemfile and run bundle install.
gem 'jwt'Then require it. The README shows a plain require, which is what you need in a non-Rails context.
require 'jwt'A first real use is an HMAC token, the case the README uses for its own example. The payload is a hash, the secret is a string, and the third argument is the algorithm name.
payload = { data: 'test' }
hmac_secret = 'my$ecretK3y'
token = JWT.encode(payload, hmac_secret, 'HS256')Decoding returns a two element array: the payload first, the header second. Note that the third argument is true, which tells the gem to verify the signature, and the fourth is the options hash carrying the algorithm.
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
# => [
# {"data"=>"test"}, # payload
# {"alg"=>"HS256"} # header
# ]For asymmetric setups the README generates an RSA key pair with OpenSSL and passes the private key to encode and the public key to decode. The same shape applies to ECDSA with OpenSSL::PKey::EC.generate and to the PS family. If you are verifying tokens from an identity provider, that public-key path is the one you will use, and you will be pasting a PEM rather than generating a key inline.
The failure modes that bite in production
The most common mistake is treating the algorithm option as optional or as something to derive from the token header. The README explicitly warns against dynamically picking the algorithm, and the linked Auth0 write-up on algorithm confusion is the reason. If your decode call reads alg from the untrusted header and passes it back in, you have built the vulnerability the gem is warning you about.
The second failure mode is the NONE algorithm leaking into a code path that also serves production. It is useful for fixtures and local development, and it is a complete bypass if it is reachable from an endpoint. Nothing in the library prevents you from accepting it; you have to not pass it.
The third is the v3 EdDSA move. Code written against a 2.x gem that called EdDSA through the core library will not behave the same after the upgrade, because the algorithm now lives in jwt-eddsa. The repository ships an UPGRADING.md specifically to cover major version transitions, and that file is the thing to read before you bump the major version in a Gemfile.
Finally, expiry is a claim, not a mechanism. The gem will decode an expired token unless you ask it to check, and it has no opinion about clock skew between your issuer and your verifier. If you need leeway, that is configuration you add, not something the library infers.
Where ruby-jwt is the wrong tool: if you want a full authorization server with token issuance, revocation, consent screens and discovery documents, this is a signing and verification primitive, not that product. Reaching for it to build an OAuth provider means writing the provider yourself.
How it compares to JWT frameworks that ship a whole auth stack
The realistic alternative in the Ruby world is not another low-level JWT library; it is a framework that bundles token handling with user management. The Devise plus jwt integration is the clearest example, and the difference is architectural rather than cosmetic. Devise owns the user model, the password strategy and the session lifecycle, and the JWT piece becomes a token transport plugged into that lifecycle. You get revocation hooks and a place to hang refresh logic because there is a database behind it.
ruby-jwt has no database behind it. That means it is trivially composable into a service that only verifies tokens minted by someone else, which is a case the Devise path handles poorly because it assumes it is the issuer. It also means that if you choose ruby-jwt for an app that needs revocation, you will write the revocation store yourself, and you will discover that a stateless token plus a revocation list is a stateful system wearing a stateless costume.
There is also the question of algorithm coverage. A framework integration inherits whatever the underlying library supports, so the practical difference is not coverage but control. With ruby-jwt you see every option in the call site. With a framework you see a configuration block, and the algorithm choice may be several layers away from the code that runs it.
Maintenance cost, version pinning and the licence
The repository is active: the last push was on 2026-09-11, matching the v3.3.0 release, and there is a v2.10.3 maintenance release from 2026-05-22 alongside the v3.2.0 line. That two-track pattern is worth noticing. If you are on 2.x you are on a branch that still receives releases, but new work lands on 3.x.
The upgrade cost is concentrated in major versions. The README points at CHANGELOG.md for the full set of changes and at UPGRADING.md for moving between majors, and the EdDSA relocation is the concrete example of why that guide exists. Budget for it as a real task, not a version bump.
The licence is MIT. That is permissive and permissive in the same way as most of the Ruby ecosystem, which matters if you are vendoring the gem or shipping a fork. This is not legal advice; if your organisation has a policy on permissive licences, run the MIT text through it rather than taking my summary.
The dependency surface is small, which is the strongest maintenance argument in its favour. A JWT library that pulls in a large tree of transitive dependencies is a supply chain problem. This one leans on OpenSSL, which you already have.
Editorial conclusion
Adopt ruby-jwt if you are building token issuance or verification in Ruby and you want a library that stays close to OpenSSL and RFC 7518 rather than wrapping it in a framework abstraction. Do not adopt it if you expect it to manage token revocation, key rotation schedules, or session storage; it does none of that. Before you ship, verify three things in your own code: that every JWT.decode call passes a hard coded algorithm in the options hash, that your Gemfile pins a major version because v3 moved EdDSA out to a separate gem, and that your test suite actually exercises an expired token and a token signed with the wrong algorithm. If those three hold, the gem's surface area is small enough to audit in an afternoon.
Frequently asked questions
What is ruby-jwt mainly used for?
It encodes and decodes JSON Web Tokens in Ruby, following RFC 7519. In practice that means signing a payload hash into a token string and verifying a token string back into a payload and header.
Is JWT the same as OAuth?
No. The README describes ruby-jwt as an implementation of the RFC 7519 OAuth JSON Web Token standard, which means it handles the token format, not the OAuth authorisation flows themselves.
Is JWT expired?
The library does not decide that for you. Expiry is a claim inside the payload, and the gem has no opinion about clock skew between issuer and verifier, so any leeway is configuration you add yourself.
Can I use a JWT as an API key?
The gem will happily encode a payload and hand you a token string, and it can verify that string later, but it stores nothing. Revocation, rotation and storage are outside its scope, so a JWT used as an API key is only as revocable as the system you build around it.
Official sources
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.
[](https://hysenlabs.com/projects/jwt-ruby-jwt)