An Interest In:
Web News this Week
- April 18, 2024
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
- April 13, 2024
- April 12, 2024
React-Rails authentication API with Devise and Devise-jwt(Back-end) part.
Introduction
I know that you might tried a lot of articles and searched a lot and you may be tired, or my article may be long and you may want to jump to the code right away but let me tell you that this article is all you need to build a complete back-end authentication for your website.
But first let me tell you a brief history about authentication API and how it works.
It is this simple steps:
1- User logs in to the website
2- You create a token for the user and send it back.
3- with each request the user perform you send back the token.
Explanation of the above steps:
1-2- When the user logs in or signs up into your website your back-end no matter which program you use(Rails, Node...) will create a token and send it back to the user in the Response headers called authorization.
3- with each request that the user perform in order for the back-end to know that this person is logged in and it is the correct user, we send back the token in the request headers mainly like Authorization: Bearer Your token here, the back-end se the token and if it was the right token, it will allow you to perform your request, pretty simple isn't it? :).
Lets begin.
- First lets create a new project, so run this:
Rails new App-Name --api -d postgresql
then cd inside the project.
we will use devise and devise-jwt and rack-cors gems for authentication, so put these gems inside the Gemfile:
Gem 'devise'Gem 'devise-jwt'Gem 'rack-cors'gem 'dotenv-rails' // for hiding variables
Then run
bundle Install or bundle
config/initializers/cors.rb is generated by default when using the--api to create an app. Otherwise you need to create it.
Update it like this. The key here is allowing all origins to make requests:
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: %w(Authorization), methods: :any, expose: %w[Authorization] endend
Using origins * is for our convenience. When deploying to production, set origins to the URL of your front-end app. Otherwise the whole internet will be able to hit your API. Though in some cases thats desirable.
Lets install Devise
run this command:
Rails g devise:install
Now create a model and migration with devise, and migrate to generate tables.
rails g devise User rails db:setup
Update the generated User model with the following code:
class User < ApplicationRecord devise :database_authenticatable, :jwt_authenticatable, :registerable, jwt_revocation_strategy: JwtDenylistend
Create another model file called jwt_denylist.rb and paste in the following:
class JwtDenylist < ApplicationRecord include Devise::JWT::RevocationStrategies::Denylist self.table_name = 'jwt_denylist'end
The included module was previously called Devise::JWT::RevocationStrategies::Blacklist so you may see that in older tutorials.
Create a migration to go along with it.
$ rails g migration CreateJwtDenylist
Update it to this.
class CreateJwtDenylist < ActiveRecord::Migration[6.1] def change create_table :jwt_denylist do |t| t.string :jti, null: false t.datetime :exp, null: false end add_index :jwt_denylist, :jti endend
And migrate.
$ rails db:migrate
This sets up a table which tracks JWT tokens that have been logged out and should no longer have access to the app.
Controllers
Create a sessions controller at /controllers/users/sessions_controller.rb. We want to override the default devise sessions controller so we can specify a custom response on log-in and log-out.
class Users::SessionsController < Devise::SessionsController respond_to :json private def respond_with(resource, _opts = {}) render json: { message: 'You are logged in.' }, status: :ok end def respond_to_on_destroy log_out_success && return if current_user log_out_failure end def log_out_success render json: { message: "You are logged out." }, status: :ok end def log_out_failure render json: { message: "Hmm nothing happened."}, status: :unauthorized endend
Create a new registration controller, /controllers/users/registrations_controller.rb.
class Users::RegistrationsController < Devise::RegistrationsController respond_to :json private def respond_with(resource, _opts = {}) register_success && return if resource.persisted? register_failed end def register_success render json: { message: 'Signed up sucessfully.' } end def register_failed render json: { message: "Something went wrong." } endend
Add one more controller, controllers/members_controller.rb, so we can test logged-in VS logged-out behaviour on an endpoint that required authenticating.
class MembersController < ApplicationController before_action :authenticate_user! def show render json: { message: "Yeppa you did it" } endend
More Devise Setup
Update config/initializers/devise.rb. Add this to the file inside the config block.
config.jwt do |jwt| jwt.secret = ENV['RAILS-SECRET-KEY']end
This tells Devise-JWT to use a secret key specified in our credentials file to build tokens.
Now generate a secret key. And note the output. Well add this into our .env file, so run:
rake secret
In the root of the project create a .env file and put this:
RAILS-SECRET-KEY = Your secret key from above command
Note: If you publish this website and upload it to heroku, do not forget to add a Config variable to your app in heroku, simply go to heroku, go to your app, click on setting, click on config vars then put your RAILS-SECRET-KEY then your secret key.
Routes
Update your routes so they point to your new controllers, rather than to the default devise controllers.
Rails.application.routes.draw do devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations' } get '/member-data', to: 'members#show'end
Now you will have these end-points:
http//localhost:3000/users/sign_in Rooute ==> Sign in Method ==> POST Body ==> { "user": { "email": "[email protected]", "password": "12345678" } } Response token ==> data.headers.authorizationhttp//localhost:3000/users Route ==> Sign up Method ==> POST Body ==> { "user": { "email": "[email protected]", "password": "12345678" } } Response token ==> data.headers.authorizationhttp//localhost:3000/member Route ==> To know if user logged in? Method ==> GET headers ==> token: token you saved from log in or sign up user Response ==> data.data.message=> 'yeppa you did it.'http//localhost:3000/users/sign_out Route ==> To know if user logged in? Method ==> DELETE headers ==> token: token you saved from log in or sign up user Response ==> data.data.message=> 'You are logged out.'
Now you can use postman,Insomnia... or any other tool to test the endpoints, just don't forget that except signin and signup you need to send the token in the header like this:
Authorization: Bearer Your token here
You may ask what will happen to the token when the user signs out? do you remember the JWT_denylist model that we created!,
each time the user logs out rails puts his token inside that table and each time you log in and have a request with a token, rails compare your token with the tokens inside the deny_list table if it matches one of them it means you are logged out and you are not authorized.
Original Link: https://dev.to/arikarim/react-rails-authentication-api-with-devise-and-devise-jwt-back-end-part-dj
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To