神刀安全网

An Introduction to Using JWT Authentication in Rails

An Introduction to Using JWT Authentication in Rails

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.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » An Introduction to Using JWT Authentication in Rails

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址