Rails API + JWT Authentication
How to build simple Rails API authentication using JSON Web Token (JWT)
What we need
- Ruby 2.5.1
- Rails 5.2.1
- Insomnia / Postman
REST API table
Okay now we’re ready to generate the Rails project, type this on your terminal
$ rails new rails-jwt --api
Add JSON Web Token (JWT) and bcrypt gem
- JWT: Open industry standard (RFC 7519) for representing claims securely between two parties
- bcrypt : Password hashing algorithm
# Use Json Web Token (JWT) for token based authentication
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
and then install dependencies by typing this on your terminal
$ bundle install
# config/routes.rbRails.application.routes.draw do
resources :users, param: :_username
post '/auth/login', to: 'authentication#login'
get '/*a', to: 'application#not_found'
In the routes.rb, we defined routes for users using resources. resources syntax helps us for generating REST API design for the user using _username as a parameter. So it will look like our REST API table above.
Create JsonWebToken class
SECRET_KEY is the key for encoding and decoding tokens. In the code above, we assign a secret key generated by default by rails application into the SECRET_KEY variable. SECRET_KEY must be secret and not to be shared. Every time we’re doing some encoding and decoding using JWT, we need to specify the SECRET_KEY. By grouping and encapsulating the JWT encoding and decoding mechanism in this class, we will reduce a couple of codes that have responsibility for doing encoding and decoding jobs, because we don’t need to specify SECRET_KEY every time. The decode and encode function above is defined as a static function because it will give flexibility for doing encoding and decoding jobs without instantiating the JsonWebToken object.
self.encode function has 2 parameters. first payload and second exp. Payload is a key-value object for holding data that want to be encoded. exp stand for expiration for holding expiration time token. if exp is not specified it will give you the default value in 24 hours or one day.
In self.decode function we decoded the token given by the user and get the first value then assign it to a decoded variable, the first value contains a payload that we had already encoded before and the second value contain information about the algorithm that we use for encoding and decoding token.
Create authorize_request function
authorize_request function has responsibility for authorizing user requests. first, we need to get a token in the header with ‘Authorization’ as a key. with this token now we can decode and get the payload value. In this application, we define user_id in the payload. You should not include the user credentials data into the payload because it will cause security issues, you can include data that is needed to authorize the user. When performing JsonWebToken.decode function, it will return JWT::DecodeError if there was an error like token was expired, token not valid, token missing, etc. After we got user_id from the payload then we will try to find the user by id and assign it into current_user variable, If the user does not exist it will return ActiveRecord::RecordNotFound and it will render an error message with HTTP status unauthorized.
Create user model
$ rails g model user name:string username:string email:string password_digest:string
add user validation
Create user controller
$ rails g controller users
Add Create, Read, Update, Delete (CRUD ) functionality
Create authentication controller
$ rails g controller authentication
Implement login feature
In JWT there is no way to invalidate token, you can use one of these approaches to implement the logout feature :
1. Remove token from the client, but token still valid, in my opinion, you should use short time period token.
2. Add token into blacklist, when token added into blacklist token still valid until expiration time but you can deny this request from accessing the resource.
Now you can test your application response with insomnia or postman