[英]Rails 6 API + React + Google login: how to authorize user to access certain server routes after login?
Please let me know if you need more information.如果您需要更多信息,请告诉我。 I'm building a website with a React frontend and Rails 6, which is in api-only mode.我正在构建一个带有 React 前端和 Rails 6 的网站,该网站处于 api-only 模式。 I've added Google login to the frontend using the react-google-login component.我已经使用 react-google-login 组件将 Google 登录添加到前端。 Once a user has logged in on the frontend, I send the ID token provided by Google to the server, which validates it using the google-id-token gem.用户登录前端后,我将 Google 提供的 ID 令牌发送到服务器,服务器使用 google-id-token gem 对其进行验证。 This all works fine, but afterwards, I'd like the user to be able to post to certain routes on the server.这一切都很好,但之后,我希望用户能够发布到服务器上的某些路由。 From googling, I've found a few different solutions, but they all seem to involve sending cookies back and forth.通过谷歌搜索,我找到了一些不同的解决方案,但它们似乎都涉及来回发送 cookies 。
Using Rails session, store user ID.使用 Rails session,存储用户 ID。 If configuration is set up correctly, this is sent to the frontend as an HttpOnly cookie, which (as long as credentials: true) sends back the cookie in each following get/post request.如果配置设置正确,它将作为 HttpOnly cookie 发送到前端,该 cookie(只要凭据:true)在每个后续的 get/post 请求中发回 cookie。
Using Rails cookies, store user ID or JWT as an HttpOnly cookie.使用 Rails cookies,将用户 ID 或 JWT 存储为 HttpOnly cookie。 Seems basically the same as above but with extra work?似乎与上述基本相同,但需要额外的工作?
Using Authorize header and JWT.使用授权 header 和 JWT。 Not 100% clear on how this works - how does Rails get the JWT to the frontend - also cookies?不是 100% 清楚这是如何工作的——Rails 如何将 JWT 送到前端——还有 cookies?
I've tried all 3 to varying extents, but spent the most time on the first method.我已经在不同程度上尝试了所有 3 种方法,但在第一种方法上花费的时间最多。 However, I can't seem to get Rails to send the session[:token] (that I set after user authentication using Google's ID token) to the frontend.但是,我似乎无法让 Rails 将 session[:token](我在使用 Google 的 ID 令牌进行用户身份验证后设置)发送到前端。 I'm open to switching to another method or one that I haven't listed.我愿意切换到另一种方法或我没有列出的方法。 Here is what my code looks like.这是我的代码的样子。
# application.rb
module MyBackendApi
class Application < Rails::Application
# ... other configs
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'https://my-frontend-app.netlify.app'
resource '*',
credentials: true,
headers: :any,
methods: [:get, :post]
end
end
config.hosts << "my-backend-api.herokuapp.com"
config.hosts << "localhost"
end
end
I also have include::ActionController::Cookies
in ApplicationController.我在 ApplicationController 中也有include::ActionController::Cookies
。
Below is how I authenticate the user using google-id-token and set the session hash.下面是我如何使用 google-id-token 验证用户并设置 session hash。
class UsersController < ApplicationController
def signin
validator = GoogleIDToken::Validator.new
begin
payload = validator.check(user_params[:idtoken], ENV['GOOGLE_CLIENT_ID'])
user_id = payload['sub']
user = User.find_by(user_id: user_id)
if user.nil?
user = User.create({
user_id: user_id
})
end
session[:user_id] = user_id
render json: {status: :ok}
rescue GoogleIDToken::ValidationError => e
puts "Cannot validate: #{e}"
render json: {message: 'Bad Request', reason: 'Error validating Google token ID'}, status: :bad_request
end
end
Here is how I check whether a user is logged in or not.这是我检查用户是否登录的方法。 The method logged_in_user
is used as a before_action
method for the appropriate controller actions.方法logged_in_user
用作适当的 controller 操作的before_action
方法。
def current_user
user_id = session[:user_id]
if !user_id.nil?
user ||= User.find_by(user_id: user_id)
end
end
def logged_in?
!current_user.nil?
end
def logged_in_user
unless logged_in?
render json: {message: 'Not logged in'}, status: :forbidden
end
end
Now for my frontend code.现在是我的前端代码。 Below is how I authenticate the user (taken almost directly from the Google docs).以下是我对用户进行身份验证的方式(几乎直接取自 Google 文档)。 Note that /googlesignin
routes to users#signin
in the backend API.请注意, /googlesignin
路由到后端 API 中的users#signin
signin。
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://my-backend-api.herokuapp.com/googlesignin');
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('idtoken=' + idtoken);
Then, I try to post to a /submit
route in the backend API.然后,我尝试发布到后端 API 中的/submit
路由。
let headers = new Headers({
'Content-Type': 'application/json'
});
fetch('https://my-backend-api.herokuapp.com/submit', {
method: 'POST',
mode: 'cors',
headers: headers,
cache: 'no-cache',
redirect: 'follow',
referrer: 'no-referrer',
credentials: 'include',
body: JSON.stringify(this.state.selected),
})
.then(function (response) {
let data = response.json()
console.log(data)
return data;
})
I also tried the following, with axios:我还使用 axios 尝试了以下操作:
axios.post('https://my-backend-api.herokuapp.com/submit', {
submission: this.state.selected,
}, {
withCredentials: true,
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
})
As I'm very new to cookies, I'm not sure whether I should be able to see the session[:user_id] cookie in Chrome devtools (application tab) or not.由于我对 cookies 非常陌生,因此我不确定是否应该能够在 Chrome 开发工具(应用程序选项卡)中看到 session[:user_id] cookie。 I don't see it, so I assume that the frontend is either not accepting or not receiving the cookie from the backend.我没有看到它,所以我假设前端要么不接受要么不接收来自后端的 cookie。 I'm not sure though.不过我不确定。
Any help is super appreciated, I've been struggling with this issue for a while, with no idea what I could be doing wrong.非常感谢任何帮助,我一直在努力解决这个问题,不知道我可能做错了什么。 I have been finding user auth to be a little overwhelming thus far.到目前为止,我一直发现用户身份验证有点压倒性。 Again, I'm willing to switch to another method.同样,我愿意改用另一种方法。 Please let me know if I should post more information.如果我应该发布更多信息,请告诉我。 Thank you!谢谢!
Update: One thing that works is to manually set a JWT Access-Token
header on Rails, and on the frontend, take that header and put it into a cookie.更新:有效的一件事是在 Rails 上手动设置 JWT Access-Token
header,在前端,将 header 放入 cookie 中。 Then when I send later requests, I stick the cookie into an Authorization
header, which I check for and validate in Rails.然后,当我稍后发送请求时,我将 cookie 粘贴到Authorization
header 中,我在 Rails 中检查并验证它。 But, is this safe?但是,这样安全吗? Intuitively, it doesn't seem secure?直觉上,它似乎不安全?
I don't think you need to use sessions at all, as that is the main idea by using Rails in API mode.我认为您根本不需要使用会话,因为这是在 API 模式下使用 Rails 的主要思想。
I remember I did something similar like that a few years ago(having the API(Rails API without devise) in one subdomain and the fronted(Backbone.js) in a separate one), in this repository我记得几年前我做过类似的事情(在一个子域中使用 API(Rails API),在一个单独的子域中使用前端(Backbone.js)),在这个存储库中
current_user
based on a token included on each request(This token was generated as a result of the Google authentication interaction)根据每个请求中包含的令牌,它看起来如何current_user
(此令牌是由于 Google 身份验证交互而生成的)https://github.com/heridev/rails_public_api/blob/master/app/controllers/application_controller.rb#L18
https://github.com/heridev/rails_public_api/blob/master/app/controllers/api/sessions_controller.rb#L4
Here is another gist you can take as a reference这是您可以作为参考的另一个要点
In general, the workflow looks like this:一般来说,工作流程如下所示:
Google
and received as a query string in the Single Page App
of your preference.然后从Google
返回一个令牌,并在您偏好的Single Page App
中作为查询字符串接收。onLoad
, it uses that token
to make an HTTP request to the backend API in a separate subdomain.在该路由onLoad
上,它使用该token
向单独子域中的后端 API 发出 HTTP 请求。searches/creates
the user and saves that token in the right record.在后端 API 中,我们验证 Google 令牌(通过向 Google 发出第二个 HTTP 请求最有可能),如果有效,它将searches/creates
用户并将该令牌保存在正确的记录中。API
, the frontend includes the google_token
, so we can search the actual user by that Google token.在对API
的每个后续请求中,前端都包含google_token
,因此我们可以通过该 Google 令牌搜索实际用户。current_user
in the backend it is just a matter of making the User.google_token
field as nil
要在后端注销current_user
,只需将User.google_token
字段设为nil
I'm planning to work on a similar application, but with a more recent Stack(React 17 and Rails 6), if this gets more attention, I can come back in a few and share more details on how that ended up.我计划开发一个类似的应用程序,但使用更新的 Stack(React 17 和 Rails 6),如果这得到更多关注,我可以回来分享更多关于它如何结束的细节。
Last words: No no need for JWT
or any devise stuff(devise_token_auth) unless you need to support also a user/password
authentication strategy.最后的话:不需要JWT
或任何 devise 东西(devise_token_auth),除非您还需要支持user/password
身份验证策略。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.