[cover image by Henri Guérin at pixels.com]
I am currently working on developing and api using grape and devise jwt for user user authentication.
Configure devise jwt is pretty straightforward all you need to do is just follow the instruction in the readme.
Install gems
gem 'grape'
gem 'devise'
gem 'devise-jwt'
bundle install
In app/api/api.rb
helpers AuthHelpers
helpers do
def unauthorized_error!
error!('Unauthorized', 401)
end
end
mount V1::UserRegistrationApi
In app/api/auth_helpers.rb
module AuthHelpers
def current_user
Warden::JWTAuth::UserDecoder.new.call(
token, :user, nil
)
rescue
unauthorized_error!
end
def token
auth = headers['Authorization'].to_s
auth.split.last
end
end
In app/models/user.rb
class User < ApplicationRecord
include Devise::JWT::RevocationStrategies::Allowlist
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
**:jwt_authenticatable, jwt_revocation_strategy: self**
end
Generate AllowlistedJwt
bin/rails g model AllowlistedJwt
class CreateAllowlistedJwts < ActiveRecord::Migration[6.1]
def change
create_table :allowlisted_jwts do |t|
t.string :jti, null: false
t.string :aud
t.datetime :exp, null: false
t.references :user, foreign_key: { on_delete: :cascade }, null: false
t.timestamps
end
add_index :allowlisted_jwts, :jti, unique: true
end
end
In config/initializers/devise.rb
config.jwt do |jwt|
jwt.secret = Rails.application.credentials.devise_jwt_secret_key!
jwt.expiration_time = 3600
end
In app/api/v1/user_registration_api.rb
# frozen_string_literal: true
module V1
class UserRegistrationApi < Grape::API
namespace :user do
namespace :register do
before do
@user_mobile_number = UserRegistrationWithMobileNumberService.new params[:mobile_number]
end
after do
header 'Authorization', @user_mobile_number.token
end
post do
@user_mobile_number.register
end
end
put :verify do
current_user.verify(params[:code])
end
end
end
end
In app/services/user_registration_with_mobile_number_service.rb
class UserRegistrationWithMobileNumberService
attr_reader :mobile_number, :token
def initialize(mobile_number)
@mobile_number = mobile_number
end
def register
user = User.find_or_initialize_by mobile_number: mobile_number
if user.save
@token, payload = Warden::JWTAuth::UserEncoder.new.call(user, :user, nil)
user.on_jwt_dispatch(@token, payload)
# TODO: UserRegistrationJob.perform_later(user.id)
end
user
end
end
In spec/api/v1/user_registration_api_spec.rb
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe V1::UserRegistrationApi, '/api/v1/user/register' do
let(:mobile_number) { '01234567' }
context 'with phone number' do
it 'creates new user' do
expect do
post '/api/v1/user/register', params: { mobile_number: mobile_number }
end.to change(User, :count).by 1
end
it 'responses the new created user' do
post '/api/v1/user/register', params: { mobile_number: mobile_number }
expect(json_body).to include mobile_number: mobile_number
expect(json_body).to include status: 'pending'
expect(json_body[:code]).to be_present
end
it 'responses with jwt authorization token' do
post '/api/v1/user/register', params: { mobile_number: mobile_number }
expect(jwt_token).to match /(^[\w-]*\.[\w-]*\.[\w-]*$)/
end
context 'when mobile number is already registered' do
let!(:user) { create(:user, mobile_number: mobile_number)}
it 'responses with jwt token' do
post '/api/v1/user/register', params: { mobile_number: mobile_number }
expect(jwt_token).to match /(^[\w-]*\.[\w-]*\.[\w-]*$)/
end
end
end
context 'when confirm' do
before do
post '/api/v1/user/register', params: { mobile_number: mobile_number }
end
context 'with correct code' do
it 'changes status from pending to confirmed' do
put '/api/v1/user/verify', params: { code: json_body[:code] }, headers: { 'Authorization': "Bearer #{jwt_token}" }
expect(json_body(reload: true)[:status]).to eq 'confirmed'
end
end
context 'with wrong code' do
it 'unable to confirm' do
put '/api/v1/user/verify', params: { code: 'wrong-code' }, headers: { 'Authorization': "Bearer #{jwt_token}" }
expect(json_body(reload: true)[:status]).to eq 'pending'
end
end
context 'without authorized jwt token header' do
it 'responses unauthorized' do
put '/api/v1/user/verify', params: { code: json_body[:code] }
expect(response).to be_unauthorized
end
end
end
end
Thanks for reading~
Top comments (1)
You have a good and usefull case here, I just think you should improve the formatting of the post. Thanks for sharing!