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.
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
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
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.).
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.
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"}}
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.