If you’re new to building API’s with Rails you’ve probably wondered how to authenticate requests made to the API to ensure that they are coming from the correct source with correct permissions. Since API’s are stateless applications they do not have the ability to create sessions for users. (Read about how Rails Sessions work here) This creates some challenges when trying to handle authentication because the app isn’t able to remember a user’s session data from one request to the next.
Enter Json Web Tokens. Json Web Tokens (JWT) are a self contained authentication method designed for stateless authentication. A self contained authentication method is one that does not require any storage on the back-end to verify the authenticity of the request. All of the data necessary to authenticate the request is contained right inside the token!
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
That is a JSON web token. If you look closely you will notice that it is punctated by two periods, breaking it up into three sections: Header, Payload, and Signature. The first section, the header, contains information about the hashing algorithm use to encode encode the token and the token’s type. The second section, the payload, contains the token’s metadata, referred to as ‘claims.’ Claims can contain any information that you wish althought for best performance it is best to keep them succinct. Some typical claims include the token’s issuer, its expiration date and the user’s account type. The final section, the signature, is used to verify the authenticity of the token and it’s contents. It contains the token’s header and payload hashed with the algorithm specified in the header and your applications unique secret key.
There are a numerous ways to implement JWT authentication in your API including a couple of Gems that abtract away most of the process. For the sake of understanding, we are going to handroll JWT in this tutorial. Let’s start by adding a user model to your API:
rails g model User
Add the following columns to the users tables:
create_table :users do |t|
t.string :email, null: false
t.string :password_digest, null: false
t.timestamps
end
Run rake db:migrate
to migrate your new users table.
You don’t have to, but it would be a good idea to add some validations to the email attribute in your user model:
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: /@/
Uncomment or add bcrypt
to your gem file.
In the user model add:
class User < ApplicationRecord
has_secure_password
...
Now we need to add an endpoint to create new users:
rails g controller Users
In UsersController.rb
make a create actions:
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
render json: { message: 'User created successfully' },
status: :created
else
render json: { errors: user.errors.full_messages },
status: :bad_request
end
end
private
def user_params
params.require(:user)
.permit(:email,
:password,
:password_confirmation)
end
end
Now lets build an endpoint for authenticating a user:
rails g controller authentication_controller
In authentication_controller.rb
:
class AuthenticationController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
auth_token = JsonWebToken.encode({user_id: user.id})
render son: {auth_token: auth_token}, status: :ok
else
render json: {error: 'Login Unsuccessfull'}, status: :unauthorized
end
end
You’ll notice a couple of methods in that action that you likely haven’t seen before. That’s because we are going to build them right now.
In your gemfile add gem jwt
and then bundle install
.
Then in your lib directory lets build a JsonWebToken class to handle creating and decoding web tokens.
class JsonWebToken
def self.encode(payload)
payload.reverse_merge!(meta)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
JWT.decode(token, Rails.application.secrets.secret_key_base)
end
def self.valid_payload(payload)
if expired(payload) || payload['iss'] != meta[:iss] || payload['aud'] != meta[:aud]
return false
else
return true
end
end
def self.meta
{
exp: 7.days.from_now.to_i,
iss: 'issuer_name',
aud: 'client',
}
end
def self.expired(payload)
Time.at(payload['exp']) < Time.now
end
end
Finally we need to add some methods to our application controller to use our Json Web Tokens for authentication.
In application_controller.rb:
protected
def authenticate_request!
if !payload || !JsonWebToken.valid_payload(payload.first)
return invalid_authentication
end
load_current_user!
invalid_authentication unless @current_user
end
def invalid_authentication
render json: {error: 'Invalid Request'}, status: :unauthorized
end
private
def payload
auth_header = request.headers['Authorization']
token = auth_header.split(' ').last
JsonWebToken.decode(token)
rescue
nil
end
def load_current_user!
@current_user = User.find_by(id: payload[0]['user_id'])
end
With this set up you are able to use ‘authenticate_request’ as a before_action for any endpoints that you want to be protected by authentication.
More Resources:
https://www.sitepoint.com/authenticate-your-rails-api-with-jwt-from-scratch/ https://github.com/jwt/ruby-jwt https://github.com/nsarno/knock
This is great. Had to use Rails.application.secret_key_base. Put the JsonWebToken class in Models too.