Ruby on Rails API with JWT Auth Tutorial

Our Mission

Starting Up Our Project

rails new notesapi --database=postgresql --api

Configuring your dependencies

gem 'bcrypt', '~> 3.1.7'
gem 'rack-cors'
gem 'jwt'

CORS

Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end

Setting up Auth Routes

Rails.application.routes.draw do
resource :users, only: [:create]
post "/login", to: "users#login"
get "/auto_login", to: "users#auto_login"
end

Creating the User Model

rails g model User username:string password_digest:string age:integer

Securing our User Model

class User < ApplicationRecord
has_secure_password
end

The User Controller

rails g controller Users

Migrating the model to your database

default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: database_name
username: test
password: test
host: localhost
port: 5432
test:
<<: *default
database: database_name
production:
<<: *default
url: <%= ENV['DBURI'] %>
rails db:create && rails db:migrate

Seed some data

user = User.create(username: "alexmerced", password: "pineapple", age: 35)
rails db:seed

The Application Controller

class ApplicationController < ActionController::API
before_action :authorized
def encode_token(payload)
JWT.encode(payload, 'yourSecret')
end
def auth_header
# { Authorization: 'Bearer <token>' }
request.headers['Authorization']
end
def decoded_token
if auth_header
token = auth_header.split(' ')[1]
# header: { 'Authorization': 'Bearer <token>' }
begin
JWT.decode(token, 'yourSecret', true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def logged_in_user
if decoded_token
user_id = decoded_token[0]['user_id']
@user = User.find_by(id: user_id)
end
end
def logged_in?
!!logged_in_user
end
def authorized
render json: { message: 'Please log in' }, status: :unauthorized unless logged_in?
end
end

SO LET’S BREAK DOWN THESE METHODS:

fetch("/", {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `bearer ${JWT_TOKEN}`,
},
body: JSON.stringify(requestBody),
})

The Users Controller

class UsersController < ApplicationController
before_action :authorized, only: [:auto_login]
# REGISTER
def create
@user = User.create(user_params)
if @user.valid?
token = encode_token({user_id: @user.id})
render json: {user: @user, token: token}
else
render json: {error: "Invalid username or password"}
end
end
# LOGGING IN
def login
@user = User.find_by(username: params[:username])
if @user && @user.authenticate(params[:password])
token = encode_token({user_id: @user.id})
render json: {user: @user, token: token}
else
render json: {error: "Invalid username or password"}
end
end
def auto_login
render json: @user
end
private def user_params
params.permit(:username, :password, :age)
end
end

Time to test our API Auth

rails s -p 4000

Making the Notes item

rails g scaffold note message:string user:references

The Result

class Note < ApplicationRecord
belongs_to :user
end
class NotesController < ApplicationController
before_action :set_note, only: [:show, :update, :destroy]
# GET /notes
def index
@notes = Note.all
render json: @notes
end
# GET /notes/1
def show
render json: @note
end
# POST /notes
def create
@note = Note.new(note_params)
if @note.save
render json: @note, status: :created, location: @note
else
render json: @note.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /notes/1
def update
if @note.update(note_params)
render json: @note
else
render json: @note.errors, status: :unprocessable_entity
end
end
# DELETE /notes/1
def destroy
@note.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_note
@note = Note.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def note_params
params.require(:note).permit(:message, :user_id)
end
end
class CreateNotes < ActiveRecord::Migration[6.0]
def change
create_table :notes do |t|
t.string :message
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
Rails.application.routes.draw do
resources :notes
resource :users, only: [:create]
post "/login", to: "users#login"
get "/auto_login", to: "users#auto_login"
end

Adding auth to notes

before_action :authorized
# GET /notes
def index
@notes = Note.where(user_id: @user.id)
render json: @notes
end
# GET /notes/1
def show
render json: @note
end
# POST /notes
def create
@note = Note.new(note_params)
@note.user_id = @user.id
if @note.save
render json: @note, status: :created, location: @note
else
render json: @note.errors, status: :unprocessable_entity
end
end

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store