JWT in Rails
Imagine you have a magic library card ๐.
- Every time you visit the library (website), instead of writing your name and address every time, you just show your magic card.
- The librarian instantly knows who you are and what you can borrow โ because all the info is already inside that card.
- The card is signed in a special way so no one can change it without breaking the magic seal.
Thatโs JWT (JSON Web Token) โ a digital magic card that proves who you are without asking the server to look you up every time.
Why We Need JWT
Without JWT:
- Every time you talk to the server, it must look up your session in a database.
- This makes servers slow and dependent on shared memory or a central store.
With JWT:
- The token itself carries the identity and permissions.
- No database lookup needed โ the server just checks the signature.
Problems JWT Solves
- Stateless Authentication โ No need to store session data on server.
- Cross-Service Login โ Easily pass user identity between microservices.
- Mobile / SPA Auth โ Works perfectly for APIs (no browser cookie dependency).
- Secure Info Sharing โ Can contain small bits of verified data that canโt be tampered with.
When to Use JWT
โ Best for:
- APIs & microservices
- Single Page Applications (React/Vue)
- Mobile app authentication
- Temporary access (e.g., password reset link)
โ Not ideal for:
- Storing large data inside the token
- Long-lived sessions without refresh
- Sensitive data unless encrypted
How JWT Works
- User logs in with username/password
- Server verifies credentials
- Server creates JWT with user info + signs it with secret key
- Server sends JWT back to user
- User stores JWT (usually in browser)
- For future requests: User sends JWT in request header
- Server verifies JWT signature (without checking database)
- If valid: Server trusts the user info in the JWT
Rails Example
1. Add the JWT gem
gem 'jwt'
2. Create a JWT service
class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
end
end
3. In your authentication controller
class AuthController < ApplicationController
def login
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
token = JsonWebToken.encode(user_id: user.id)
render json: { token: token }
else
render json: { error: 'Invalid credentials' }
end
end
end
4. Middleware to verify tokens
class ApplicationController < ActionController::API
before_action :authenticate_user
private
def authenticate_user
header = request.headers['Authorization']
token = header.split(' ').last if header
begin
decoded = JsonWebToken.decode(token)
@current_user = User.find(decoded[:user_id])
rescue JWT::DecodeError
render json: { error: 'Invalid token' }
end
end
end
Structure
JWT has 3 parts (Base64 encoded, separated by dots):
<Header>.<Payload>.<Signature>
Example:
// Header (type & algo)
{
"alg": "HS256",
"typ": "JWT"
}
// Payload (claims)
{
"sub": "user123",
"role": "admin",
"exp": 1713456000
}
// Signature = HMACSHA256(Base64(header) + "." + Base64(payload), secret)
Core Concepts
- Statelessness: No server storage needed for auth state.
-
Claims:
- Registered claims (
exp
,iat
,iss
) - Public claims (
role
,email
) - Private claims (custom, agreed between parties)
- Registered claims (
-
Signing Algorithms:
HS256
โ Symmetric key (secret shared)RS256
โ Asymmetric (private/public key pair)
-
Verification:
- Check signature matches with known key
- Check
exp
(expiry) andnbf
(not before) claims
Security Pitfalls
- Never store sensitive info (like passwords) in JWT payload โ itโs Base64-encoded, not encrypted.
- Always set expiry (
exp
) to avoid infinite tokens. - Use HTTPS โ JWTs can be stolen if sent over plain HTTP.
- Invalidate tokens via short expiry + refresh tokens.
โก TL;DR
- JWT = signed info package for stateless authentication.
- Removes the need for server session storage.
- Great for APIs, SPAs, and microservices.
- Must handle expiry, HTTPS, and secret key management for security.