Authentication & Authorize in Ruby on Rails (part 1 --> without JWT)

سلام، همونطور که اطلاع دارین(یعنی امیدوارم که اطلاع داشته باشین!)، میخوایم راجع به Authentication و Authorize در Ruby on Rails صحبت کنیم.


قبل از اینکه شروع کنیم، خدمتتون عرض کنم که این مقاله رو در دو قسمت مینویسم.

قسمت اول راجع به Authentication و Authorize صحبت میکنیم.

قسمت دوم راجع به این که چطوری JWT ایجاد کنیم و ازش استفاده کنیم، صحبت خواهیم کرد.




در ابتدا میریم سراغ ساخت یه پروژه ی کوچیک در rails و سپس میریم سراغ Authentication و Authorize.

راستی بیاین قبل از اینکه شروع کنیم، یه لیوان چای لاهیجان(یکی از شهرستان های استان گیلان معروف به عروس گیلان) آماده کنیم و همینطور که این پروژه رو میبریم جلو، چای هم نوش جان کنیم (:

میتونین از طریق گیت هابم یعنی از اینجا به این پروژه دسترسی داشته باشید.

خب یه پروژه به اسم authentication_authorize_project به صورت زیر میسازیم.

rails new authentication_authorize_project --api

اگه میبینین از api-- استفاده کردیم، به این دلیل که نمیخوایم برامون view ایجاد بشه. (اگه هم نمیدونین چیه هم الان مهم نیست ولی بعدا حتما گوگل کنین).

بعد از اینکه پروژه ساخته شد، با دستور cd authentication_authorize_project بِرید داخل پوشه ی پروژه و دستور bundle install رو بزنین.

حالا برای اینکه مطمئن باشین که همه چی اوکیه، دستور rails s رو بزنین و در مرورگرتون آدرس http://127.0.0.1:3000 رو بزنین. اگه همه چی اوکی بوده باشه، باید صفحه ای زیبا که نوشته hey! You're on Rails رو ببینین (:


در این پروژه ما قصد داریم تا user رو authentication و authorize کنیم. برای این کار یک مدلِ User که فیلد های name, email, password داره رو به صورت زیر میسازیم.

rails g model User name email password_digest

فقط یه دستور کوچیک rails db:migrate هم بزنین که بریم سراغ مرحله ی اصلی.

ما برای اینکه authentication و authorize انجام بدیم، از 'gem 'bcrypt استفاده میکنیم. برای اینکار باید بریم درون فایل Gemfile.rb در پروژه و 'gem 'bcrypt رو که در این فایل کامنت هست رو از حالت کامنت بیرون بیاریم. حالا دومرتبه دستور bundle install رو میزنیم تا gem مورد نظر ما نصب بشه.

درون مدل User، متد has_secure_password رو اضافه میکنیم که تا مطمئن باشیم password به صورت encrypted درون دیتابیس ذخیره بشه. ناگفته نماند که این متد جزئی از bcrypt است.

همچنین متد has_secure_password، علاوه بر کاری که در بالا خدمتتون عرض کردم، password و password_confirmation رو هم مقایسه میکنه که با هم match باشند. منتها یه مشکلی که در اینجا وجود داره، اگه nil ،password_confirmation باشه، باز هم رکورد ما در دیتابیس ذخیره میشه. برای جلوگیری از این کار، یه validates به مدل User به صورت زیر اضافه میکنیم.

validates :password_confirmation, presence: true

حالا باید یه کنترولر برای user بسازید. برای اینکار از دستور زیر اسفاده کنین.

rails g controller users

فایل routes.rb رو باز کنین و resource کنترولر جدید رو به صورت زیر درونش اضافه کنین.

Rails.application.routes.draw do
    resources :users
end

الان بِرید داخل کنترولرِ user و یک action به اسم create ایجاد کنید. حالا میتونین مثل قطعه کد پایین، sign up کنین و user جدید رو در دیتابیس ذخیره کنین (:

class UsersController < ApplicationController
  before_action :user_params

  def create
    @user = User.new(user_params)
    if @user.save
      render json: { message: ['Saved'] }, status: 200
    else
      render json: { message: ['Not Saved'] }, status: 400
    end
  end

  private

  def user_params
    params.permit(:name, :email, :password, :password_confirmation)
  end
end

من برای request زدن از postman استفاده میکنم، شما میتونین از هرچی دلتون میخواد استفاده کنین.


راستی، چون ما موقع ایجاد مدل User برای password از password_digest استفاده کردیم، تنها یه فیلد password که encrypted هم هست در دیتابیس ذخیره میشه.

Authentication:

در ابتدا یه کنترولر به اسم Sessions با اکشن های create و destroy به صورت زیر ایجاد میکنیم.

rails g controller Sessions create destroy

به کد زیر با دقت نگاه کنین. در واقع ما یه سرچ زدیم تا user مورد نظرمون رو از طریق اسمی که از پارامتر بهمون میرسه، پیدا کنیم. چون ما از bcrypt استفاده کردیم، یه متدی به نام authenticate بهمون میده و کارش اینه، passwordی که بهش میدیم رو با passwordی که در دیتابیس ذخیره شده بود رو با هم مقایسه میکنه و true یا false بر میگردونه. اگه true بر گردونه، ما ID یوزر مورد نظر رو در session ذخیره میکنیم.

در قسمت destroy هم session یوزری که وجود داره رو nil میکنه.

class SessionsController < ApplicationController
  def create
    user = User.find_by( email: params[:email] )
    if user.try(:authenticate, params[:password])
      session[:user_id] = user.id
      render json: { message: ['Welcome'] }, status: 200
    else
      render json: { message: ['Invalid user/password combination'] }, status: 400
    end
  end

  def destroy
    session[:user_id] = nil
    render json: { message: ['Logged out'] }, status: 200
  end
end

بریم routes های کنترولر session هم set کنیم تا بتونیم login و logout انجام بدیم.

controller :sessions do
  post 'login' => :create
  delete 'logout' => :destroy
end

Authorize:

ما در اینجا بررسی میکنیم که دسترسی یک کاربر یا یوزر به کنترولر های مختلف سایت ما چطور هست.

چون تمام کنترولر های ما از ApplicationController ارث میبرند، لذا ما متد authorize رو در ApplicationController ایجاد میکنیم.

class ApplicationController < ActionController::API
  before_action :authorize

  protected

  def authorize
    unless User.find_by(id: session[:user_id])
      render json: { message: ['Please log in'] }, status: 401
    end
  end
end

متد authorize قبل از شروع همه ی کنترولر ها اجرا میشه. حتی زمانی هم که login می کنیم، دومرتبه به ما ارور میده که please login. برای حل این مشکل دستور زیر رو، در کنترولر هایی که نمیخوایم قبل از شروعشون، متد authorize اجرا بشه، مینویسیم.

skip_before_action :authorize

اگه سوالی هست خوش حال میشم بتونم کمکتون کنم (:

امیدوارم از چایتون لذت برده باشید ((: