August 07, 2019
gem 'devise'
gem 'jwt'
group :development, :test do
gem 'dotenv-rails'
end
bin/rails generate devise:install
bin/rails generate devise User
bin/rails db:migrate
Dotenv::Railtie.load if defined?(Dotenv)
namespace :api, defaults: { format: :json } do
devise_for :users
end
bin/rails routes | grep users
SECRET_KEY_BASE=hmmmmm
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
def generate_jwt
JWT.encode({
sub: id,
exp: 60.days.from_now.to_i
}, ENV['SECRET_KEY_BASE'])
end
end
# frozen_string_literal: true
class Api::SessionsController < Devise::SessionsController
skip_before_action :verify_authenticity_token, raise: false
skip_before_action :verify_signed_out_user, raise: false
respond_to :json
def create
user = User.find_by_email(email_param)
if user.try(:valid_password?, password_param)
@current_user = user
cookies[:authorization] = { value: @current_user.generate_jwt, httponly: true }
else
render json: { message: 'email or password is invalid', errors: [] }, status: 422
end
end
def destroy
cookies.delete :authorization
render json: { message: 'signed out' }
end
private
def email_param
params.require(:user).fetch(:email, '').try(:strip).try(:downcase)
end
def password_param
params.require(:user).fetch(:password, '')
end
def auth_token
request.headers['Authorization'].try(:split, ' ').try(:last) || cookies[:authorization]
end
end
json.user do |json|
json.token @current_user.generate_jwt
json.id @current_user.id
json.email @current_user.email
json.updated_at @current_user.updated_at
json.created_at @current_user.created_at
end
class Api::PasswordsController < Devise::PasswordsController
skip_before_action :verify_authenticity_token, raise: false
respond_to :json
def create
missing_email and return if email_param.blank?
user = User.find_by(email: email_param)
if user
raw, enc = Devise.token_generator.generate(User, :reset_password_token)
user.reset_password_token = enc
user.reset_password_sent_at = Time.now.utc
if user.save
# TODO: send email to user.email with reset password url "/users/reset-password/#{user.id}/#{raw}"
else
render_error user.errors
return
end
end
render json: { message: 'Please check your email' }
end
def update
token = Devise.token_generator.digest(User, :reset_password_token, reset_password_token_param)
@user = User.find_by({ id: user_id })
user_not_found and return unless @user
invalid_reset_token and return if @user.reset_password_token != token
invalid_reset_token and return if @user.nil?
password_confirmation_does_not_match and return if password_param != password_confirmation_param
@user.password = password_param
unless @user.save
render_error @user.errors
end
cookies[:authorization] = { value: @user.generate_jwt, httponly: true }
end
private
def email_param
params.require(:user).fetch(:email)
end
def reset_password_token_param
params.require(:user).fetch(:reset_password_token)
end
def user_id
params.require(:user).fetch(:id)
end
def password_param
params.require(:user).fetch(:password)
end
def password_confirmation_param
params.require(:user).fetch(:password_confirmation)
end
def missing_email
render json: { message: 'Please provide an email address', errors: [] }, status: 422
end
def invalid_reset_token
render json: { message: 'Invalid reset token', errors: [] }, status: 422
end
def user_not_found
render json: { message: 'User not found' }, status: 404
end
def password_confirmation_does_not_match
render json: { message: 'Password does not match password confirmation', errors: [] }, status: 422
end
end
json.user do |json|
json.id @user.id
json.email @user.email
json.updated_at @user.updated_at
json.created_at @user.created_at
end
# frozen_string_literal: true
class Api::RegistrationsController < Devise::RegistrationsController
skip_before_action :verify_authenticity_token, raise: false
respond_to :json
def create
build_resource(registration_params)
if resource.save
@current_user = resource
cookies[:authorization] = { value: @current_user.generate_jwt, httponly: true }
else
render_error resource.errors
end
end
private
def registration_params
params.require(:user).permit(:email,
:password,
:password_confirmation)
end
def render_error(errors)
render json: {
errors: errors.messages.map { |id, err| { field: id.to_s, message: err.to_sentence } },
message: errors.full_messages.to_sentence
}, status: 422
end
end
json.user do |json|
json.token @current_user.generate_jwt
json.id @current_user.id
json.email @current_user.email
json.updated_at @current_user.updated_at
json.created_at @current_user.created_at
end
# frozen_string_literal: true
class ApplicationController < ActionController::Base
helper_method :current_user
def auth_token
request.headers['Authorization'].try(:split, ' ').try(:last) || cookies[:authorization]
end
def require_auth
render status: 401, json: { message: 'Please sign in' } unless current_user
end
def current_user
@current_user ||= User.find_by(id: @current_user_id)
end
def authenticate_user!
return unless auth_token
unless AuthToken.find_by(auth_token: auth_token).try(:active)
render status: 401, json: { message: 'Please sign in' }
return
end
begin
jwt_payload = JWT.decode(auth_token, ENV['SECRET_KEY_BASE']).first
@current_user_id = jwt_payload['sub']
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
render status: 401, json: { message: 'Please sign in' }
end
end
def signed_in?
@current_user_id.present?
end
end
class ApiController < ApplicationController
before_action :authenticate_user!
skip_before_action :verify_authenticity_token, raise: false
def require_auth
return if current_user
render status: 401, json: { message: 'Please sign in' }
end
end
class Api::ExampleController < ApiController
def index
render json: { user: current_user }
end
end