Rails API using JWT as auth framework this will have user with roles
Things we will cover:
-
Project generation
rails new rails-jwt-api-sample --api
rails g model User first_name middle_name last_name token email password_digest
rails db:migrate
gem 'bcrypt'
bundle install
class User < ApplicationRecord has_secure_password end
gem 'jwt'
bundle install
class JsonWebToken class << self def encode(payload, exp = 24.hours.from_now) payload[:exp] = exp.to_i JWT.encode(payload, Rails.application.secrets.secret_key_base) end def decode(token) body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0] HashWithIndifferentAccess.new body rescue nil end end end
config.autoload_paths << Rails.root.join('lib')
update Gemfile to include simple command is similar to the role of a helper, but instead of facilitating the connection between the controller and the view, it does the same for the controller and the model
gem 'simple_command'
bundle install
create new command has to take the user's e-mail and password and return the user if the credentials match
class AuthenticateUser prepend SimpleCommand def initialize(email, password) @email = email @password = password end def call JsonWebToken.encode(user_id: user.id) if user end private attr_accessor :email, :password def user user = User.find_by_email(email) return user if user && user.authenticate(password) errors.add :user_authentication, 'invalid credentials' nil end end
class AuthorizeApiRequest prepend SimpleCommand def initialize(headers = {}) @headers = headers end def call user end private attr_reader :headers def user @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token @user || errors.add(:token, 'Invalid token') && nil end def decoded_auth_token @decoded_auth_token ||= JsonWebToken.decode(http_auth_header) end def http_auth_header if headers['Authorization'].present? return headers['Authorization'].split(' ').last else errors.add(:token, 'Missing token') end nil end end
class AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :unauthorized
end
end
end
post 'authenticate', to: 'authentication###authenticate'
we need to expose the current_user to 'persist' in order to have current_user available to all controllers, it has to be declared in the ApplicationController
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
@current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless @current_user
end
end
enum access_level: [:sales, :admin, :operations]
t.integer :access_level
JsonWebToken.encode(user_id: user.id, user_email: user.email, user_access_level: user.access_level) if user
admin = User.create!(email: "admin@mail.com" , password: "123456" , password_confirmation: "123456", access_level: :admin, first_name: "John", middle_name: "Daniel", last_name: "Doe")
let's (not...lol, as you will see we will perform each task separately) scaffold a resource so see how authorizarion works
rails g model Product name:string description:text
rails g controller Product
before_action :authenticate_request
def index
@products = Product.all
render json: @products
end
get 'produtcs', to: 'produtc#index'
Product.create!(name:"Asimo", description:"Honda's first robot")
rails db:drop
rails db:create
rails db:migrate
rails db:seed
-
Ruby version
-
System dependencies JWT - JWT official website
-
Configuration
-
Database creation
rails db:drop
rails db:create
- Database initialization
rails db:migrate
rails db:seed
-
How to run the web api rails server -p 3110
-
How to test the web api
curl -H "Content-Type: application/json" -X POST -d '{"email":"admin@mail.com","password":"123456"}' http://localhost:3110/authenticate
{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2VtYWlsIjoiYWRtaW5AbWFpbC5jb20iLCJ1c2VyX2FjY2Vzc19sZXZlbCI6ImFkbWluIiwiZXhwIjoxNTE4NzM1ODYyfQ.HowwXLphSllo6hDK_t6hW4rq3hadSmxIFJA2D15KYCg"}
curl http://localhost:3110/products
{"error":"Not Authorized"}
curl -H "Authorization: TOKEN_HERE " http://localhost:3000/products
curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2VtYWlsIjoiYWRtaW5AbWFpbC5jb20iLCJ1c2VyX2FjY2Vzc19sZXZlbCI6ImFkbWluIiwiZXhwIjoxNTE4NzM1ODYyfQ.HowwXLphSllo6hDK_t6hW4rq3hadSmxIFJA2D15KYCg" http://localhost:3110/products
[{"id":1,"name":"Asimo","description":"Honda's first robot","created_at":"2018-02-14T22:44:24.204Z","updated_at":"2018-02-14T22:44:24.204Z"},{"id":2,"name":"Mind Storm","description":"Llego's IoT for kids","created_at":"2018-02-14T22:44:24.207Z","updated_at":"2018-02-14T22:44:24.207Z"},{"id":3,"name":"Raspberry Pi","description":"Micro controller","created_at":"2018-02-14T22:44:24.209Z","updated_at":"2018-02-14T22:44:24.209Z"}]
-
Services (job queues, cache servers, search engines, etc.)
-
Deployment instructions
If you missed something, the project has been uploaded on GitHub. If you have any questions, do not hesitate to ask in the comments or feel free to message me on Github.