【Rails】GraphQL + Devise认证
首先
我经常看到使用devise_token_auth作为Rails API的身份认证方法,但很少见到直接使用现有的devise实现的方法,所以我写了这篇文章。
前提 tí)
-
- すでに運用開始している Railsアプリケーションに追加で実装する(APIモードではない)
devise を使用する(devise_token_auth は使用しない)
既存の User モデルに適用する
GraphQL を実装する
フロント側の実装は割愛
组成
我將認證流程整理在圖表中。

关于第③项,由于是前端实现,所以在本文中省略介绍。
实施流程
-
- Rails アプリケーション実装
-
- GraphQL 実装
- Devise 実装
Rails 应用程序的实现
新建一个Rails应用程序,并创建一个任务模型。
$ rails new graphql_devise_sample
$ rails g model Task body:string
$ rails db:create
$ rails db:migrate
GraphQL的实现
有关GraphQL的实现方法的详细说明,请参阅以下内容。(本文中提到了命令,但详细说明被省略了。)
-
- 初めてのGraphQL with Rails
https://qiita.com/sazumy/items/58106174392516da5f1d
在 Gemfile 文件中添加 GraphQL 的 gem。
gem 'graphql'
group :development do
gem 'graphiql-rails'
end
执行bundle install命令,安装GraphQL,并应用于Task模型。
$ bundle install
$ rails g graphql:install
$ rails g graphql:object Task
添加查询。
module Types
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)`
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField
field :task, Types::TaskType, null: true do
description "Find Task by ID"
argument :id, ID, required: true
end
def task(id:)
Task.find(id)
end
field :all_tasks, Types::TaskType.connection_type, null: true do
description 'All Tasks'
end
def all_tasks
Task.all
end
end
end
如果可以做到这一步,您可以访问localhost:3000/graphiql,并确认能够执行查询。
{
todayTasks {
edges{
node{
id
body
createdAt
}
}
}
}
设计实施
请参阅以下内容以了解Devise的具体实现方法。(本文中也提供了相应的命令,但没有详细解释。)
-
- 【Rails】deviseを導入してみる
https://qiita.com/Hal_mai/items/350c400e8763ce0487a3
首先,在Gemfile中添加devise gem。
gem 'devise'
执行bundle install命令,安装Devise并将其应用于User模型。
$ bundle install
$ rails g devise:install
$ rails g devise user
$ rails g migration add_access_token_to_user
在用户模型中添加access_token。
class AddAccessTokenToUser < ActiveRecord::Migration
def change
add_column :users, :access_token, :string
end
end
$ rails db:migrate
当用户创建时,将创建访问令牌。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
after_create :update_access_token!
def update_access_token!
self.access_token = "#{self.id}:#{Devise.friendly_token}"
save
end
end
我們將在 application_controller.rb 中添加一個方法,以確認在 API 執行時,access_token 在認證時一起傳遞過來。
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
def authenticate_user_from_token!
auth_token = request.headers['Authorization']
if auth_token
authenticate_with_auth_token(auth_token)
else
authenticate_error
end
end
private
def authenticate_with_auth_token(auth_token)
unless auth_token.include?(':')
authenticate_error
return
end
_, token = auth_token.split(' ')
user_id = token.split(':').first
user = User.where(id: user_id).first
if user && Devise.secure_compare(user.access_token, token)
# User can access
sign_in user, store: false
else
authenticate_error
end
end
##
# Authentication Failure
# Renders a 401 error
def authenticate_error
render json: { error: t('devise.failure.unauthenticated') }, status: 401
end
end
我会添加路由。
Rails.application.routes.draw do
# 以下を追加
namespace :api do
resource :users, only: [:create]
resource :login, only: [:create], controller: :sessions
end
end
在这里,为了创建access_token获取API和用户注册API,我们分别创建两个文件。
app/controllers/api/sessions_controller.rb
ログイン(access_token取得)(構成図の①)
app/controllers/api/users_controller.rb
サインアップ(ユーザー登録)
module Api
class SessionsController < ApplicationController
def create
@user = User.find_for_database_authentication(email: params[:email])
return invalid_email unless @user
if @user.valid_password?(params[:password])
sign_in :user, @user
render json: @user, root: nil
else
invalid_password
end
end
private
def invalid_email
warden.custom_failure!
render json: { error: 'invalid_email' }
end
def invalid_password
warden.custom_failure!
render json: { error: 'invalid_password' }
end
end
end
module Api
class UsersController < ApplicationController
def index
@users = User.all
end
def create
@user = User.new(user_params)
if @user.save!
render json: @user
else
render json: { error: 'user_create_error' }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end
end
以上是实施部分。接下来要确认执行结果。
执行结果
我們將確認以下4點是否在實施中正確執行。
-
- ユーザー登録API
access_token取得API
access_token を持っていないと、GraphQL の実行結果が取得できないこと
access_token を持っていると、GraphQL の実行結果が取得できないこと
首先,在开发环境中使用rails s命令启动服务器。然后,请使用WebAPI开发工具,例如POSTMAN,执行以下操作。
用户注册API
通过向以下URL发送POST请求来确认可以进行用户注册。
-
- URL
localhost:3000/api/users?user[email]=test@example.com&user[password]=aaaaaa
获得API的访问令牌。
通过执行POST请求到以下URL,可以确认可以获取access_token。
-
- URL
localhost:3000/login?email=test@example.com&password=aaaaaa
如果没有access_token,无法获取GraphQL的执行结果。
通过指定以下的URL、Authorization请求头部和正文内容,进行POST请求来确认无法获取执行结果。
-
- URL
localhost:3000/graphql
Body
GraphQL
Query
以下のクエリを記載すること
{
todayTasks {
edges{
node{
id
body
createdAt
}
}
}
}
如果拥有 access_token,则无法获取 GraphQL 的执行结果。
通过指定以下URL、Authorization请求头和Body,执行POST操作以确认获取执行结果。
-
- URL
localhost:3000/graphql
Authorization リクエストヘッダー
Type
Bearer Token
Token
1:fkib5vzMa1YjqyMnbMUo(access_token取得API実行時に取得したaccess_tokenを記載すること)
Body
GraphQL
Query
以下のクエリを記載すること
{
todayTasks {
edges{
node{
id
body
createdAt
}
}
}
}
GraphQL + Devise的实施和确认处理到此为止。
总结
我写了关于使用原始的方法来实现devise作为Rails API的身份验证方法的实现方法。虽然篇幅有点长,但如果能对某人有所帮助,我会很高兴。
顺便说一下,最近我终于开始使用GitHub Copilot了,它大大提高了我的生产效率,我很喜欢。
请参考
-
- 初めてのGraphQL with Rails
https://qiita.com/sazumy/items/58106174392516da5f1d
【Rails】deviseを導入してみる
https://qiita.com/Hal_mai/items/350c400e8763ce0487a3
Rails5 API + devise でユーザーの認証と作成機能を実装した API を作成する
GitHub (skedesu / graphql_devise_sample )
https://github.com/skedesu/graphql_devise_sample