Key Takeaways
- JSON Web Tokens (JWTs) are used for standards-based token handling and verification in API development. These tokens are comprised of three strings (header, payload, signature) and are self-contained, carrying information such as payload and signatures.
- JWTs are used in Rails through libraries. The tutorial demonstrates the creation of a simple Rails application using the Devise gem for authentication and the jwt gem for creating and verifying JWT tokens.
- The tutorial explains the process of setting up JWT authentication in Rails, creating and decoding JWT tokens, and using these tokens for user authentication. It also covers the creation of a User model for authentication and the integration of jwt into the application.
- JWT authentication is secure and stateless, making it a suitable choice for authentication in Rails API. It allows user information to be embedded directly into the token, reducing the need for database lookups. It is important to set an expiration time on tokens and use a strong secret key for signing tokens.
With the advent of Single Page Applications (SPA) and mobile applications, APIs have come to the forefront of web development. As we develop APIs to support our SPA and mobile apps, securing the APIs has been a major pain area. Token-based authentication is one of the most-favored authentication mechanisms, but tokens are prone to various attacks. To mitigate that, one has to implement ways to fix the issues, which often leads to one-off solutions that make tokens non-exchangeable between diverse systems. JSON Web Tokens (JWT) were created to implement standards-based token handling and verification that can be exchanged between diverse systems without any issue.
What is JWT?
JWTs carry information (called “claims”) via JSON, hence the name JSON Web Tokens. JWT is a standard and has been implemented in almost all popular programming languages. Hence, they can be easily used or exchanged in systems implemented in diverse platforms.
JWTs are comprised of plain strings, so they can be easily exchanged in a URL or a HTTP header. They are also self-contained and carry information such as payload and signatures.
Anatomy of a JWT
A JWT (pronounced ‘JOT’) consists of three strings separated by ‘.’:
aaaaa.bbbbbbb.ccccccc
The first part is the header, second part is the payload, and third part is the signature.
The header consists of two parts:
- The type of token, i.e. ‘JWT’
- The hashing algorithm used
For example:
{
"typ": "JWT",
"alg": "HS256"
}
The header is base64 encoded which results in:
aeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
This is the first part of the token.
The second part of a JWT is the payload. This part carries the interesting information in the token, also called as JWT Claims. Claims are of three types – private, public, and registered.
- Registered Claims are claims whose names are reserved but are not mandatory to be used. Examples being – iss, sub, aud, etc.
- Private Claims are names that are agreed upon between two parties and can collide with other public claims. Must be used with caution.
- Public Claims – Claims that we can create as per our authentication requirements, such as username, user information, etc.
We can create a sample payload like so:
{
"iss": "sitepoint.com",
"name": "Devdatta Kane",
"admin": true
}
This will be encoded as –
ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ
This becomes the second part of token.
The third and, possibly, the most important part is the Signature. It’s a hash of three components: the header, the payload, and the secret. We run the combined string of the header and the payload through an HMACSHA256 function with ‘secret’ as the server-side secret. Like so:
require "openssl"
require "base64"
var encodedString = Base64.encode64(header) + "." + Base64.encode64(payload);
hash = OpenSSL::HMAC.digest("sha256", "secret", encodedString)
Since only the server knows the secret, no one can tamper with the payload and the server can detect any tampering using the signature.
Our signature looks as follows:
2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37
Now we have got all three parts of our JWT. Combining the three parts, we get:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ.2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37
This is our complete JWT which can be used for further requests.
Using JWT in Rails
JWT has libraries for almost all platforms and Ruby is no exception. We will create a simple Rails application which uses the excellent Devise gem for authentication and the jwt gem for creating and verifying JWT tokens.
Let’s create a sample Rails application with a contact model and CRUD. The app uses Rails 4.2 and SQlite:
rails new jwt_on_rails
After the application is generated, create a Home controller which we will use to check our authentication. Here is our home_controller.rb
in the app/controllers
. Like so:
class HomeController < ApplicationController
def index
end
end
Map the HomeController
to /home
in config/routes.rb
:
Rails.application.routes.draw do
get 'home' => 'home#index'
end
Check how its working:
rails s
Point your favorite browser to http://localhost:3000/ and check if everything is working properly.
We have our base application ready. Now, add Devise to our application. First, we will add the Devise and jwt gems in our Gemfile
. Like so:
gem 'devise'
gem 'jwt'
Install them using:
bundle install
Now let’s create the Devise configuration files:
rails g devise:install
We will create the Devise User
model and migrate the database:
rails g devise User
rake db:migrate
Our User
model is in place, which we will use for authentication. It’s time to integrate jwt into our application. First, we will create a class named JsonWebToken
in lib/json_web_token.rb
. This class will encapsulate the JWT token encoding and decoding logic. Like so:
class JsonWebToken
def self.encode(payload)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
return HashWithIndifferentAccess.new(JWT.decode(token, Rails.application.secrets.secret_key_base)[0])
rescue
nil
end
end
Add an initializer for including the JsonWebToken
class in config/initializers/jwt.rb
. Like so:
require 'json_web_token'
We will now add some helper methods in the ApplicationController
class which we will use in AuthenticationController
class.
In app/controllers/application_controller.rb
:
class ApplicationController < ActionController::Base
attr_reader :current_user
protected
def authenticate_request!
unless user_id_in_token?
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
return
end
@current_user = User.find(auth_token[:user_id])
rescue JWT::VerificationError, JWT::DecodeError
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
end
private
def http_token
@http_token ||= if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
end
end
def auth_token
@auth_token ||= JsonWebToken.decode(http_token)
end
def user_id_in_token?
http_token && auth_token && auth_token[:user_id].to_i
end
end
What we’ve done here is added a few helper methods like authenticate_request!
which will act as a before_filter
to check user credentials. We will create an AuthenticationController
to handle all authentication requests to the API. Like so:
In app/controllers/authentication_controller.rb
:
class AuthenticationController < ApplicationController
def authenticate_user
user = User.find_for_database_authentication(email: params[:email])
if user.valid_password?(params[:password])
render json: payload(user)
else
render json: {errors: ['Invalid Username/Password']}, status: :unauthorized
end
end
private
def payload(user)
return nil unless user and user.id
{
auth_token: JsonWebToken.encode({user_id: user.id}),
user: {id: user.id, email: user.email}
}
end
end
Here we have added AuthenticationController
to implement the authentication endpoint. It uses Devise to authenticate the user and issue a JWT if the credentials are valid.
We will now update our routes.rb
to add the authentication endpoint. Like so:
Rails.application.routes.draw do
post 'auth_user' => 'authentication#authenticate_user'
get 'home' => 'home#index'
end
Also, modify the HomeController
to secure it using a before_filter
and add a meaningful response in case of successful authentication:
class HomeController < ApplicationController
before_filter :authenticate_request!
def index
render json: {'logged_in' => true}
end
end
Now, create a sample user to test the authentication mechanism using Rails Console.
rails c
rails> User.create(email:'a@a.com', password:'changeme', password_confirmation:'changeme')
Start the server and check out how JWT authentication works:
rails s
Open another terminal and use cURL to test the API. First, try to authenticate without any email or password:
curl http://localhost:3000/home
The response should be {"errors":["Not Authenticated"]}
since we have not provided any credentials.
Now authenticate against the API and receive a JWT which we will use for subsequent requests:
curl -X POST -d email="a@a.com" -d password="changeme" http://localhost:3000/auth_user
You’ll receive a successful response along with a JSON Web Token and additional user information. Like so:
{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4","user":{"id":1,"email":"a@a.com"}}
Use our fresh auth_token
in the request to /home
. Like so:
curl --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4" http://localhost:3000/home
We should receive a successful login response, like:
{"logged_in":true}
Conclusion
We can now utilize this API in any Angular/React/Ember application by storing the issued JWT (in a cookie or local storage) and using it in subsequent requests. This wraps up our tutorial in which we learned how to implement JWT in a Rails application along with Devise. While this tutorial covered just the basics, it is foundational to using JWTs for API authentication.
Hope you liked this tutorial. Comments and feedback welcome, as always.
Frequently Asked Questions about Using JWT in Rails
How do I set up JWT authentication in Rails?
Setting up JWT authentication in Rails involves several steps. First, you need to add the JWT gem to your Gemfile and run bundle install. Next, you need to create a user model and controller, and set up routes for user registration and login. Then, you need to create a JWT token when a user registers or logs in, and send this token back to the user. The user will then send this token in the header of their requests to authenticate themselves. You can decode this token in your application to authenticate the user.
What are the benefits of using JWT for authentication in Rails?
JWT offers several benefits for authentication in Rails. It is stateless, meaning the server does not need to keep a record of which tokens have been issued – it can simply check the signature of the incoming token to verify it. This makes your application more scalable. JWT also allows you to embed user information directly into the token, reducing the need for database lookups.
How secure is JWT authentication?
JWT authentication is secure as long as you follow best practices. This includes using a strong secret key to sign your tokens, and not including sensitive user information in the token payload. It’s also important to set an expiration time on your tokens to reduce the impact of any tokens that might be stolen.
How do I handle token expiration with JWT?
When you create a JWT, you can set an expiration time for the token. If a user tries to authenticate with an expired token, your application should reject the request. You can then prompt the user to log in again to receive a new token.
Can I use JWT with Rails API?
Yes, JWT is a great choice for authentication with a Rails API. Because JWT is stateless, it works well with APIs, which are typically designed to be stateless. You can include the JWT in the header of your API requests to authenticate the user.
How do I test JWT authentication in Rails?
You can test JWT authentication in Rails by creating a request spec that simulates a user logging in and receiving a token. You can then use this token in subsequent request specs to authenticate the user.
Can I use JWT with Devise?
Yes, you can use JWT with Devise. You will need to override some of Devise’s default behavior to return a JWT when a user logs in, instead of using session-based authentication.
How do I decode a JWT in Rails?
You can decode a JWT in Rails using the JWT.decode method. This will return the payload of the token, which you can use to authenticate the user.
What should I do if a JWT is stolen?
If a JWT is stolen, the thief can use it to authenticate as the user until the token expires. Therefore, it’s important to set a short expiration time on your tokens. If a token is stolen, you should prompt the user to log in again and issue them a new token.
Can I include roles and permissions in a JWT?
Yes, you can include roles and permissions in the payload of a JWT. This can be a convenient way to authorize users, as you can check the user’s role or permissions directly from the token, without needing to look up this information in your database.
Devdatta Kane is a software developer and designer based in Pune, India. He works with Radinik Technologies building traceability solutions for a variety of industries. He is also the lead developer of refers2, a CRM for small businesses. He works in Ruby on Rails, but likes to dabble with various new technologies as well. An aspiring photographer and passionate traveler, he loves traveling on his motorcycle, capturing experiences through camera.