Currently, anyone can post by hitting the API, but I would like to change this to a configuration that links this to the logged-in user.
devise is the de facto standard around authentication in Rails. You can use a wide range of functions such as user creation, login / logout, password reissue, login count recording, login failure block, etc. just by entering and setting this. On the contrary, it is difficult to customize because of its multi-functionality, but ...
The token-authenticated version of devise is devise_token_auth. Since devise is derived, devise_token_auth requires devise.
Gemfile
...
+ #Authentication
+ gem "devise"
+ gem "devise_token_auth"
Install both devise and devist_token_auth.
$ rails g devise:install
$ rails g devise_token_auth:install User auth
Reference: Use [Rails] devise token auth
Config / routes.rb is rewritten at runtime, but this time I want to put it in the v1 namespace, so fix it.
config/routes.rb
# frozen_string_literal: true
Rails.application.routes.draw do
- mount_devise_token_auth_for 'User', at: 'auth'
namespace "v1" do
resources :posts
+ mount_devise_token_auth_for 'User', at: 'auth'
end
end
Also, the automatically generated file is stuck in rubocop, so fix it. It's a little rough, but I'll set exclusions for migrate and config. Other than that, we will handle it manually.
diff:.rubocop.yml
...
+ #Method length
+ Metrics/MethodLength:
+ Exclude:
+ - "db/migrate/**/*"
+
+ # AbcSize
+ Metrics/AbcSize:
+ Exclude:
+ - "db/migrate/**/*"
+
+ #Line length
+ Layout/LineLength:
+ Exclude:
+ - "config/initializers/**/*"
This time, we will use only the minimum necessary functions, so delete unnecessary initial values.
app/models/user.rb
devise :database_authenticatable, :registerable,
- :recoverable, :rememberable, :trackable, :validatable
+ :rememberable, :validatable
include DeviseTokenAuth::Concerns::User
db/migrate/xxxxxxxxxxxxxx_devise_token_auth_create_users.rb
## Database authenticatable
t.string :encrypted_password, null: false, default: ""
- ## Recoverable
- t.string :reset_password_token
- t.datetime :reset_password_sent_at
- t.boolean :allow_password_change, default: false
-
## Rememberable
t.datetime :remember_created_at
- ## Confirmable
- t.string :confirmation_token
- t.datetime :confirmed_at
- t.datetime :confirmation_sent_at
- t.string :unconfirmed_email # Only if using reconfirmable
-
- ## Lockable
- # t.integer :failed_attempts, :default => 0, :null => false # Only if
lock strategy is :failed_attempts
- # t.string :unlock_token # Only if unlock strategy is :email or :both
- # t.datetime :locked_at
-
## User Info
t.string :name
- t.string :nickname
- t.string :image
t.string :email
...
add_index :users, :email, unique: true
add_index :users, %i[uid provider], unique: true
- add_index :users, :reset_password_token, unique: true
- add_index :users, :confirmation_token, unique: true
- # add_index :users, :unlock_token, unique: true
end
After making the changes so far, migrate.
$ rails db:migrate
Try it with curl.
$ curl localhost:8080/v1/auth -X POST -H 'Content-Type: application/json' -d '{"email": "[email protected]", "password": "password", "name": "hoge"}'
{"status":"success","data":{"uid":"[email protected]","id":1,"email":"[email protected]","provider":"email","name":null,"created_at":"2020-09-08T04:40:44.659Z","updated_at":"2020-09-08T04:40:44.827Z"}}
You have successfully registered. However, if you look closely, it is null even though the name is specified by hoge.
Being able to guess the cause of this is proof that you're used to Rails.
This is because, like other controllers, the columns that can be registered with strong parameters are limited. Therefore, we will take the following measures.
app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include DeviseTokenAuth::Concerns::SetUserByToken
rescue_from ActiveRecord::RecordNotFound, with: :render_404
+ before_action :configure_permitted_parameters, if: :devise_controller?
def render_404
render status: 404, json: { message: "record not found." }
end
+
+ def configure_permitted_parameters
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
+ end
end
This makes it possible to register including the name column.
$ rails db:reset
$ curl localhost:8080/v1/auth -X POST -H 'Content-Type: application/json' -d '{"email": "[email protected]", "password": "password", "name": "hoge"}'
{"status":"success","data":{"uid":"[email protected]","id":1,"email":"[email protected]","provider":"email","name":"hoge","created_at":"2020-09-08T04:51:42.527Z","updated_at":"2020-09-08T04:51:42.698Z"}}
Reference: How to use super basic devise
Now that you have registered, we will confirm your login.
$ curl localhost:8080/v1/auth/sign_in -X POST -H 'Content-Type: application/json' -d '{"email": "[email protected]", "password": "password"}' -i
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: T4ZeomARybw3_o5nIHQAfw
token-type: Bearer
client: Fj772-EYBPnvJdETYhObyQ
expiry: 1600751367
uid: [email protected]
ETag: W/"8c41022d2e42ca28df0cb958a84ab2f4"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: d4ff732c-f6b5-4213-8984-5d9457b39cbd
X-Runtime: 0.510436
Transfer-Encoding: chunked
{"data":{"id":1,"email":"[email protected]","provider":"email","uid":"[email protected]","name":"hoge"}}
By adding the -i
option, the header information is also returned.
And at the end, the information of the user registered earlier is listed, and the header also returns with 200 OK, so you can see that you can log in normally.
Notable headers
access-token: T4ZeomARybw3_o5nIHQAfw client: Fj772-EYBPnvJdETYhObyQ uid: [email protected]
These are the three.
By including these three in the header at the time of request, it is determined that the access is for an authenticated account.
As an aside, what happens if the authentication information is incorrect? Try logging in with the wrong email or password.
$ curl localhost:8OST -H 'Content-Type: application/json' -d '{"email": "[email protected]", "password": "PASSWORD"}' -i
HTTP/1.1 401 Unauthorized
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
Cache-Control: no-cache
X-Request-Id: 12832212-9797-465b-a5b1-ecaa7e88a977
X-Runtime: 0.308726
Transfer-Encoding: chunked
{"success":false,"errors":["Invalid login credentials. Please try again."]}
It will be returned at 401.
→ Building a bulletin board API with certification authorization in Rails 6 # 11 User model test and validation added [To the serial table of contents]