Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)

Introduction

This article develops API in Rails API mode, develops front side in Vue.js 3, and uses devise_token_auth as authentication platform for SPA with token-based authentication function. It will be a tutorial to make.

I will not touch on the environment construction of Rails and Vue.js.

Create API with Rails new

This time, the project name is sample-api.

$ rails new sample-api -d postgresql -T --api

$ cd sample-api

$ rails db:create

DB uses PostgreSQL, skips the generation of the test directory, and runs it to create a Rails app template in API mode.

By specifying --api, the following settings will be made.

-Set to narrow down the middleware to be used than usual and start the application. In particular, it will stop using any middleware (such as cookie support) that is useful in browser applications.

-Inherit ApplicationController from ActionController :: API instead of normal ActionController :: Base. As with middleware, it excludes all Action Controller modules that are only used in browser applications.

· Set the generator not to generate views, helpers, or assets.

Source: API-only application by Rails

Install devise and devise_token_auth

Edit the Gemfile.

gem 'jbuilder'
gem 'rack-cors'
gem 'devise'
gem 'devise_token_auth'

$ bundle

jbuilder and rack-cors are commented out by default, so you can remove them.

To briefly explain the usage intention, jbuilder handles JSON simply in Rails, so rack-cors was introduced to make CORS (Cross Origin Resource Sharing) settings easy. I am.

What is CORS? About Hiroshi Tokumaru's You Tube was detailed. Know the principle of CORS and use it correctly

After bundle install is complete, execute the following command.

sample-api $ rails g devise:install

sample-api $ rails g devise_token_auth:install User auth

Running the above command will create a devise initializer and locale files. The following article is very detailed about how to use devise in detail.

Devise Primer 64 Recipe Part 1

Then modify the created migration file.

## Rememberable
t.datetime :remember_created_at

#add to
## Trackable
t.integer  :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string   :current_sign_in_ip
t.string   :last_sign_in_ip

## Confirmable
t.string   :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string   :unconfirmed_email # Only if using reconfirmable

After making corrections, execute the following command.

$ rails db:migrate

rack-cors settings

Edit config/initializers/cors.rb as follows.

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
    headers: :any,
    expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'],
    methods: [:get, :post, :options, :delete, :put, :patch, :head]
  end
end

By making the above settings, you will be able to communicate between different origins (communication from localhost: 4200 to localhost: 3000, etc.).

Check API communication

With the default settings, the token will be updated for each request, so mess with the config.

# config/initializers/devise_token_auth.rb

  # By default the authorization headers will change after each request. The
  # client is responsible for keeping track of the changing tokens. Change
  # this to false to prevent the Authorization header from changing after
  # each request.
  config.change_headers_on_each_request = false # <=Uncomment and fix from true to false

  # By default, users will need to re-authenticate after 2 weeks. This setting
  # determines how long tokens will remain valid after they are issued.
  config.token_lifespan = 2.weeks # <=Uncomment

At this point, let's try the authentication function.

First, execute the following command.

$ rails c

> User.create!(name: 'Test user', email: '[email protected]', password: 'password')

Then start the local server with the rails s command and run the following curl command.

$ curl -D - localhost:3000/auth/sign_in -X POST -d '{"email":"[email protected]", "password":"password"}' -H "content-type:application/json"

If you log in successfully, you should get the following values.

HTTP/1.1 200 OKn"
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: [email protected]
ETag: W/"12ac3053b26f91ca234280ac13a0790c"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 707fe01b-d25a-4167-b0f2-95e009c9271a
X-Runtime: 0.403161
Vary: Origin
Transfer-Encoding: chunked

{"data":{"id":1,"email":"[email protected]","provider":"email","uid":"[email protected]","allow_password_change":false,"name":"Test user"}}

The access-token and client values ​​will be used to determine if you are logged in.

To explain the options specified by curl,

-D - #Display the response header.
-X   #Used to specify HTTP method
-d   #Send by putting data in Request body in POST request
-H   #Add header information

I am doing.

Verify token

devise_token_auth also provides functionality for token validation.

Let's send the following curl request using the token obtained earlier.

$ curl -D - -H "access-token:Obtained token" -H "client:Obtained client" -H "expiry:Obtained expiry" -H "uid:Obtained uid" -H "content-type:application/json" localhost:3000/auth/validate_token

If successful, the following response will be returned

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: [email protected]
ETag: W/"f3e45c8f2942619bd67981aead0bc740"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0b9e57df-1f3b-4597-9c0f-01a6b3f904be
X-Runtime: 0.086486
Vary: Origin
Transfer-Encoding: chunked

{"success":true,"data":{"id":1,"provider":"email","uid":"[email protected]","allow_password_change":false,"name":"Test user"}}

Do simple CRUD with scaffold

Since we are using devise, we can use the authenticate_user! method. So, write the authenticate_user! method in the controller created using scaffold, Let's test whether the authentication judgment is done properly.

$ rails g scaffold Post title:string body:text user:references
Running via Spring preloader in process 21
      invoke  active_record
      create    db/migrate/20210112131218_create_posts.rb
      create    app/models/post.rb
       error    rspec [not found] #Because Rspec is not installed
      invoke  resource_route
       route    resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
       error    rspec [not found] #Because Rspec is not installed
      invoke    jbuilder
      create      app/views/posts
      create      app/views/posts/index.json.jbuilder
      create      app/views/posts/show.json.jbuilder
      create      app/views/posts/_post.json.jbuilder

Make some edits.

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]
  before_action :authenticate_user! # authenticate_user!To be called before each action is executed

  def index
    @posts = current_user.posts
  end

  def show
  end

  def create
    @post = Post.new(post_params)

    if @post.save
      render :show, status: :created #Delete location
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def update
    if @post.update(post_params)
      render :show, status: :ok #Delete location
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def destroy
    @post.destroy
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def post_params
      params.permit(:title, :body).merge(user: current_user) #Remove require and current_merge user
    end
end
# config/routes.rb

  scope format: 'json' do #Supports json format requests
    resources :users do
      resources :posts
    end
  end
# app/models/user.rb

has_many :posts, dependent: :destroy
# app/views/_post.json.jbuilder

json.extract! post, :id, :title, :body, :user_id, :created_at, :updated_at
# json.url post_url(post, format: :json)To delete

Execute migration in this state.

$ rails db:migrate

As before, let's check the operation with the curl command.

#create action
$ curl localhost:3000/users/1/posts -X POST -d '{"title":"Review", "body":"interesting"}' \
-H "content-type:application/json" \
-H "access-token:Obtained access-token" \
-H "client:Obtained client" \
-H "expiry:Obtained expiry" \
-H "uid:Obtained uid"

{"id":1,"title":"Review","body":"interesting","user_id":1,"created_at":"2021-01-12T22:27:28.290+09:00","updated_at":"2021-01-12T22:27:28.290+09:00"}

#index action

$ curl localhost:3000/users/1/posts -H "content-type:application/json" \
-H "content-type:application/json" \
-H "access-token:Obtained access-token" \
-H "client:Obtained client" \
-H "expiry:Obtained expiry" \
-H "uid:Obtained uid"

[{"id":1,"title":"Review","body":"interesting","user_id":1,"created_at":"2021-01-12T22:26:53.244+09:00","updated_at":"2021-01-12T22:26:53.244+09:00"}]

I passed the authentication properly and was able to get the json normally.

Next time, we will implement the Vue.js side.

Recommended Posts

Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)
[Rails withdrawal] Create a simple withdrawal function with rails
Introduced # 10 devise_token_auth to build a bulletin board API with authentication authorization in Rails 6
Create an EC site with Rails 5 ⑨ ~ Create a cart function ~
Create pagination function with Rails Kaminari
[Rails6] Create a new app with Rails [Beginner]
Make a login function with Rails anyway
[Rails] Let's create a super simple Rails API
[Rails 5] Create a new app with Rails [Beginner]
Build a bulletin board API with authentication and authorization with Rails 6 # 1 Environment construction
Build a bulletin board API with authentication authorization in Rails # 13 Add authentication header
Nuxt.js × Create an application in Rails API mode
Create authentication function in Rails application using devise
# 6 show, create implementation to build bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails 6 # 5 controller, routes implementation
[Rails] rails new to create a database with PostgreSQL
Rails6 [API mode] + MySQL5.7 environment construction with Docker
Introducing # 15 pundit to build a bulletin board API with authentication authorization in Rails 6
Create a team chat with Rails Action Cable
Build a bulletin board API with authentication authorization in Rails # 17 Add administrator privileges
Make Rails API mode support cookie (session) authentication
Create a web api server with spring boot
Create an EC site with Rails 5 ⑩ ~ Create an order function ~
I made a function to register images with API in Spring Framework. Part 1 (API edition)
Place "Create a to-do list using Rails API mode and React Hooks" on docker
Build a bulletin board API with authentication authorization in Rails 6 # 14 seed Execution time display
Create a login authentication screen using the session function
Building a bulletin board API with authentication authorization with Rails 6 Validation and test implementation of # 4 post
Build a bulletin board API with authentication and authorization with Rails # 18 ・ Implementation of final user controller
A new employee tried to create an authentication / authorization function from scratch with Spring Security
Build a bulletin board API with authentication authorization with Rails 6 # 3 RSpec, FactoryBot introduced and post model
Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post
I made a function to register images with API in Spring Framework. Part 2 (Client Edition)
[Rails DM] Let's create a notification function when DM is sent!
[Rails] Create an email sending function with ActionMailer (complete version)
[Rails] Create API to download files with Active Storage [S3]
Create a SlackBot with AWS lambda & API Gateway in Java
A series of steps to create portfolio deliverables with Rails
[Ruby on Rails] Implement login function by add_token_to_users with API
Tutorial to create a blog with Rails for beginners Part 2
Tutorial to create a blog with Rails for beginners Part 0
Create XML-RPC API with Wicket
Introduced graph function with rails
API creation with Rails + GraphQL
Create a playground with Xcode 12
Login function implementation with rails
Link API with Spring + Vue.js
Create a name input function
[SAP] Create a development environment with NW AS ABAP Developer Edition (1)
Build a SPA for Laravel 6.2 / Vue.js / Nginx / Mysql / Redis with Docker
I want to add a browsing function with ruby on rails
I implemented Rails API with TDD by RSpec. part3-Action implementation with authentication-
Create a restaurant search app with IBM Watson + Gurunavi API (with source)
(Ruby on Rails6) Create a function to edit the posted content
I tried to make a group function (bulletin board) with Rails