我使用 React 7 作为 API 与omniauth-google-oauth2、omniauth-rails_csrf_protection、devise 和 devise-jwt。
对于 React React-oauth 和 jotai。
我像这样配置我的 Rails API:
db/schema.rb
...
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "password"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.string "google_id"
t.string "google_refresh_token"
t.string "google_access_token"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
...
配置/credentials.yml.enc
...
google:
app_id: APP_ID__FROM_GOOGLE
app_secret: SECRET_FROM_GOOGLE
config/initializers/devise.rb
...
config.omniauth :google_oauth2,
Rails.application.credentials.dig(:google, :app_id),
Rails.application.credentials.dig(:google, :app_secret),
{ scope: 'email, profile',
prompt: 'select_account' }
...
配置/初始化器/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource(
'*',
headers: :any,
expose: %w[access-token Authorization client expired token-type uid],
methods: %i[get patch put delete post options show]
)
end
end
配置/routes.rb
...
devise_for :users,
defaults: { format: :json },
path: '',
path_names: {
sign_in: 'login',
sign_out: 'logout',
registration: 'signup'
},
controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations',
omniauth_callbacks: 'users/omniauth_callbacks'
}
控制器/用户/omniauth_callbacks_controller.rb
module Users
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
@user = User.from_omniauth(request.env['omniauth.auth'])
if @user.persisted?
sign_in(@user)
render json: {
data: UserSerializer.new(@user).serializable_hash[:data][:attributes],
message: 'Logged in successfully.'
}, status: :ok
else
render json: {
message: "error: #{user.errors.full_messages.join("\n")}"
}, status: :unprocessable_entity
end
end
end
end
模型/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
devise :omniauthable, omniauth_providers: [:google_oauth2]
has_many :order_configurations, dependent: :destroy
has_many :products, dependent: :destroy
has_many :product_configurations, dependent: :destroy
def self.from_omniauth(access_token)
data = access_token.info
find_or_create_by(google_id: data[:uid]) do |user|
user.email = [:email]
user.password = Devise.friendly_token[0, 20]
user.skip_confirmation!
end
end
end
对于 React 前端,我得到:
main.tsx
...
const googleClientId = SAME_APP_ID_AS_IN_RAILS_APP
...
<GoogleOAuthProvider clientId={googleClientId}>
<RouterProvider router={routes}/>
</GoogleOAuthProvider>
...
AuthPage.tsx
import { useSetAtom } from 'jotai'
import userAtom from '../stores/userAtom.ts'
import { useNavigate } from 'react-router-dom'
import { useGoogleLogin } from '@react-oauth/google'
const AuthPage = () => {
const setUser = useSetAtom(userAtom)
const navigate = useNavigate()
const login = useGoogleLogin({
onSuccess: async (response) => {
console.log(response)
const requestOptions = {
method: 'POST',
body: JSON.stringify({ 'access_token': response.access_token })
}
return fetch('http://localhost:3001/auth/google_oauth2/callback', requestOptions)
.then(response => response.json())
.then(response => {
setUser(response)
navigate('/')
})
.catch((error) => console.error(error))
},
flow: 'implicit'
})
return (
<button onClick={() => login()}> Log with google</button>
)
}
export default AuthPage
在 Rails 服务控制台中出现此错误
Started POST "/auth/google_oauth2/callback" for 127.0.0.1 at 2024-01-07 17:26:23 +0100
D, [2024-01-07T17:26:23.864119 #376013] DEBUG -- omniauth: (google_oauth2) Callback phase initiated.
E, [2024-01-07T17:26:23.864335 #376013] ERROR -- omniauth: (google_oauth2) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected
Processing by Users::OmniauthCallbacksController#failure as */*
Redirected to http://localhost:3001/login
Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 225)
请求rails API之前的console.log:
{
"access_token": "ACCESS_TOKEN_INFO",
"token_type": "Bearer",
"expires_in": 3599,
"scope": "email profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid",
"authuser": "0",
"prompt": "none"
}
在调试模式下,我尝试在调试模式下在 google_oauth2 控制器方法中读取
request.env['omniauth.auth']
,但我无法捕获他,不存在。
我尝试将流程选项从
useGoogeLogin()
更改为 auth-code
并尝试在请求标头中将 access_token
作为不记名令牌。
在 OmniauthCallbacksController 中尝试添加
skip_before_action :verify_authenticity_token, only: :google_oauth2
但是我有这个错误
D, [2024-01-07T17:18:34.428091 #18514] DEBUG -- omniauth: (google_oauth2) Callback phase initiated.
E, [2024-01-07T17:18:34.431311 #18514] ERROR -- omniauth: (google_oauth2) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected
E, [2024-01-07T17:18:34.463871 #18514] ERROR -- omniauth: (google_oauth2) Authentication failure! Before process_action callback :verify_authenticity_token has not been defined: ArgumentError, Before process_action callback :verify_authenticity_token has not been defined
ArgumentError (Before process_action callback :verify_authenticity_token has not been defined):
我现在也遇到同样的问题,请问你解决了吗?