A complete guide for building microservices with Ruby on Rails – Part III

Assumption: You are referring to this sample API application along these series of posts.

In last post we covered Why, What and How of API versioning.

In this post we will see

  • What and why of API authentication?
  • Various strategies for API authentication with recommendations.

API authentication: What and Why?

Well if you are exposing/writing API endpoints for the application which are not public, you need to authenticate all of the API endpoints to protect and secure applications data. API authentication means allowing only authenticated requests/users/consumers to access application’s data via API’s in a secure way.

In simple words API authentication is nothing but asking an API requester to prove his identity.

What are the various ways of API authentication?

There are two popular ways for API authentication, HTTP basic authentication & API Key based authentication.

HTTP basic authentication

This is the first and most simple solution for authenticating API’s. In which an API requesters pass ‘username:password’ in request ‘Authorization’ header as follows.

GET / HTTP/1.1
Host: yourhost.com
Authorization: Basic Zm9vOmJhcg==

But this is considered as less secure way of api authenticating, as it can be compromised if HTTP communication is not secure. If you are interested in implementation you can check out how to implement HTTP basic authentication in Rails

API authentication via API key/token

This is the most commonly used and recommended way of API authentication, to implement token based API authentication we will use JWT ruby implementation.

Let’s see some of the advantages using JWT based API tokens

  • It allows you to encode and decode payload (user meta information) into a secure token (random bytes) via various cryptographic algorithms.
  • The token which you get against the payload is a computed token via secret key which makes it highly secure and unpredictable.
  • Additionally JWT allows token expiration after which payload cannot be decoded which makes it un-processable.
  • As its a computed token no database storage required.

Time to implement JWT for API authentication with Rails

First step first add jwt gem into your Gemfile and ‘bundle install’

gem 'jwt'

Writing ApiKeyHandler

#app/services/api_key_handler.rb

class ApiKeyHandler
  def self.encoded_api_key(user_id)
    payload = { exp: 4.week.from_now.to_i, user_id: user_id.to_s }
    api_key = JWT.encode(payload, Rails.application.secrets.secret_key_base)
  end

  def self.decode_api_key(api_key)
    ## Sample claims hash
    #[
    #   {
    #     "exp"=>1443275296,
    #     "user_id"=>"55e1b72e84f16d427a000000"
    #   },
    #  {"typ"=>"JWT", "alg"=>"HS256"}
    #]
    JWT.decode(api_key, Rails.application.secrets.secret_key_base)
  end
end

If you have noticed ApiKeyHandler has two methods for encoding and decoding payload data which contains

  1. All the information from which we can pull out the users rest of the data.
  2. Expiration time of token.

ApiKeyHandler#encode which uses JWT#encode method which takes a payload with expiration time, a secret key for encoding and returns encoded api_key.

Similarly, ApiKeyHandler#decode which uses JWT#decode method takes encoded api_key, a secret key and returns a decoded payload data.

Using ApiKeyHandler

#app/models/user.rb

class User 
  ...
  def generate_api_key
    ApiKeyHandler.encoded_api_key(self.id)
  end
  ...
end

Returning user api key via login api

#app/controllers/v1/users_controller.rb

class V1::UsersController < V1::BaseController
  skip_before_action :authenticate!, only: [:login]
  ...
  def login
    user_params = params[:user]
    user = User.where(email: user_params[:email]).first
    unless user and user.valid_password?(user_params[:password])
      render json: { message: "Invalid credentials"}, status: 401
    else
      render json: { email: user.email, api_key: user.generate_api_key }
    end
  end
  ...
end

Note: To explain first time generation and returning of encoded api_key in response, I have briefed only login method here, to see the full implementation of user create and login flow please visit a sample application.

Did you noticed? We are skipping, authentication via skip_before_action :authenticate!, why? Left as an exercise :p.

Make login request to get a api_key

curl -X POST \
  http://localhost:3000/users/login \
  -H 'Accept: application/vnd.expense-manager.com; version=1' \
  -H 'Content-Type: application/json' \
  -d '{"user":{"email":"test0@domain.com","password":"test123"}}'

Response

{
    "email": "test0@domain.com",
    "api_key": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTAwNTU1NjAsInVzZXJfaWQiOiI1YjYxOTUwMzM4YzcyZDM2ZjI5ZmM4MDkifQ.DQN3jYq7gHn71cuEaKodywIGYH9sm0w6Q7Zz8yJ0mvY"
}

Authenticating user request via API key

#app/controllers/application_controller.rb

class ApplicationController < ActionController::API
  before_action :authenticate!
  X_API_KEY = 'X-Api-Key'
  ...
  def authenticate!
    set_current_user || render_unauthorized
  end

  def set_current_user
    return false unless api_key?
    claims = ApiKeyHandler.decode_api_key(request.headers[X_API_KEY])
    @current_user = User.find(claims[0]['user_id'])
  rescue JWT::ExpiredSignature
    render_expired_message
  rescue JWT::DecodeError
    render_decode_error
  end

  def api_key?
    request.headers[X_API_KEY].present?
  end  
  ...
end

Note: Check out the full implementation.

Our goal is to authenticate each an every request via verifying api_key, so we need to add a ‘before_action :authenticate! method in ApplicationController. In this method we are trying to decode api_key which is passed in request header using ApiKeyHandler, on success we are setting a current user otherwise render an error response.

curl -X GET \
  http://localhost:3000/users/expenses \
  -H 'Accept: application/vnd.expense-manager.com; version=1' \
  -H 'Content-Type: application/json' \
  -H 'X-Api-Key: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTAwNTY5NDYsInVzZXJfaWQiOiI1YjYxOTUwMzM4YzcyZDM2ZjI5ZmM4MDkifQ.ImCJMzOJh79_l8H53QEJ2W_S3kOouBmLx4I9wSqiTA4'

Note: Here we are passing api key as a ‘X-Api-Key’ in request header.

That’s it. We have seen how to generate and use JWT token for API authentication, we can conclude the part III of this series and see you soon with the next post on API throttling i.e. rate limiting.

If you have any queries please post in comments section and keep exploring!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.