You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails7纯API+React集成OmniAuth谷歌登录遇NoMethodError报错求助

Rails 7纯API + React OAuth登录:NoMethodError (undefined method `expired?' for nil:NilClass)

我基于Rails 7纯API应用搭配React前端实现OAuth登录,未使用Devise。此前已解决OmniAuth::NoSessionError,现在遇到新错误:NoMethodError (undefined method expired?' for nil:NilClass)`。以下是相关代码及完整堆栈信息,求技术支持。


Gem配置 (Gemfile)

gem 'rails', '~> 7.0.0'
gem 'pg', '~> 1.1'
gem 'omniauth-google-oauth2'
gem 'rack-cors'
gem 'jwt'
# 其他依赖gem

用户模型 (app/models/user.rb)

class User < ApplicationRecord
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.info.email
      user.name = auth.info.name
      # 其他字段赋值逻辑
    end
  end
end

OAuth回调控制器 (app/controllers/api/omniauth_callbacks_controller.rb)

class Api::OmniauthCallbacksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def google
    @user = User.from_omniauth(request.env['omniauth.auth'])
    token = generate_jwt_token(@user) # 自定义JWT生成方法
    render json: { user: @user, token: token }
  rescue StandardError => e
    render json: { error: e.message }, status: :unprocessable_entity
  end

  def failure
    render json: { error: 'OAuth授权失败' }, status: :bad_request
  end

  private

  def generate_jwt_token(user)
    JWT.encode({ user_id: user.id, exp: 7.days.from_now.to_i }, Rails.application.credentials.secret_key_base)
  end
end

OmniAuth初始化器 (config/initializers/omniauth.rb)

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, 
    ENV['GOOGLE_CLIENT_ID'], 
    ENV['GOOGLE_CLIENT_SECRET'],
    scope: 'email,profile',
    redirect_uri: ENV['GOOGLE_REDIRECT_URI'] # 指向Rails API回调地址
end

# 之前解决OmniAuth::NoSessionError的配置
OmniAuth.config.allowed_request_methods = [:get, :post]
OmniAuth.config.session_store = :cookie_store

路由配置 (config/routes.rb)

Rails.application.routes.draw do
  namespace :api do
    get '/auth/google/callback', to: 'omniauth_callbacks#google'
    get '/auth/failure', to: 'omniauth_callbacks#failure'
    get '/auth/google', to: 'omniauth#redirect' # 可选的跳转路由
  end
end

React前端核心代码

// 登录按钮点击事件
const handleGoogleLogin = () => {
  window.location.href = `${process.env.REACT_APP_API_URL}/api/auth/google`;
};

// 回调页面处理逻辑
useEffect(() => {
  const urlParams = new URLSearchParams(window.location.search);
  const error = urlParams.get('error');
  if (error) {
    console.error('授权失败:', error);
    return;
  }
  // 若后端处理后返回token,直接解析存储
  const token = urlParams.get('token');
  if (token) {
    localStorage.setItem('auth_token', token);
    // 跳转到首页
    window.location.href = '/';
  }
}, []);

完整错误堆栈

NoMethodError (undefined method `expired?' for nil:NilClass):

omniauth (2.1.1) lib/omniauth/strategy.rb:165:in `session'
omniauth (2.1.1) lib/omniauth/strategy.rb:455:in `session'
omniauth (2.1.1) lib/omniauth/strategy.rb:462:in `csrf_token'
omniauth (2.1.1) lib/omniauth/strategy.rb:448:in `valid_csrf_token?'
omniauth (2.1.1) lib/omniauth/strategy.rb:434:in `request_valid?'
omniauth (2.1.1) lib/omniauth/strategy.rb:398:in `callback_phase'
omniauth-google-oauth2 (1.1.1) lib/omniauth/strategies/google_oauth2.rb:92:in `callback_phase'
omniauth (2.1.1) lib/omniauth/strategy.rb:243:in `callback_call'
omniauth (2.1.1) lib/omniauth/strategy.rb:190:in `call!'
omniauth (2.1.1) lib/omniauth/strategy.rb:169:in `call'
omniauth (2.1.1) lib/omniauth/builder.rb:44:in `call'
rack (2.2.7) lib/rack/tempfile_reaper.rb:15:in `call'
rack (2.2.7) lib/rack/etag.rb:27:in `call'
rack (2.2.7) lib/rack/conditional_get.rb:40:in `call'
rack (2.2.7) lib/rack/head.rb:12:in `call'
actionpack (7.0.8) lib/action_dispatch/http/permissions_policy.rb:22:in `call'
actionpack (7.0.8) lib/action_dispatch/http/content_security_policy.rb:18:in `call'
rack (2.2.7) lib/rack/session/abstract/id.rb:266:in `context'
rack (2.2.7) lib/rack/session/abstract/id.rb:260:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/cookies.rb:689:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport (7.0.8) lib/active_support/callbacks.rb:99:in `run_callbacks'
actionpack (7.0.8) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/debug_exceptions.rb:28:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/show_exceptions.rb:32:in `call'
railties (7.0.8) lib/rails/rack/logger.rb:40:in `call_app'
railties (7.0.8) lib/rails/rack/logger.rb:27:in `call'
sprockets-rails (3.4.2) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/remote_ip.rb:93:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/request_id.rb:26:in `call'
rack (2.2.7) lib/rack/method_override.rb:24:in `call'
rack (2.2.7) lib/rack/runtime.rb:22:in `call'
activesupport (7.0.8) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/server_timing.rb:20:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/static.rb:23:in `call'
rack (2.2.7) lib/rack/sendfile.rb:110:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/host_authorization.rb:137:in `call'
rack-cors (2.0.1) lib/rack/cors.rb:100:in `call'
railties (7.0.8) lib/rails/engine.rb:530:in `call'
puma (5.6.7) lib/puma/configuration.rb:252:in `call'
puma (5.6.7) lib/puma/request.rb:77:in `block in handle_request'
puma (5.6.7) lib/puma/thread_pool.rb:340:in `with_force_shutdown'
puma (5.6.7) lib/puma/request.rb:76:in `handle_request'
puma (5.6.7) lib/puma/server.rb:441:in `process_client'
puma (5.6.7) lib/puma/server.rb:242:in `block in run'
puma (5.6.7) lib/puma/thread_pool.rb:147:in `block in spawn_thread'

排查与解决方案

从堆栈可以看出,错误根源是OmniAuth尝试调用session.expired?时,session对象为nil。结合Rails API模式的特性,可从以下方向修复:

  1. 启用Session中间件
    Rails API模式默认禁用ActionDispatch::Session::CookieStore,需在config/application.rb手动添加:

    config.middleware.use ActionDispatch::Session::CookieStore
    

    同时在config/initializers/session_store.rb配置session参数:

    Rails.application.config.session_store :cookie_store, 
      key: '_oauth_api_session',
      same_site: :lax,
      secure: Rails.env.production?,
      httponly: true
    
  2. 修正CORS配置
    确保Rails允许前端携带凭证,修改config/initializers/cors.rb:

    Rails.application.config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins process.env['FRONTEND_URL'] || 'http://localhost:3000' # 生产环境替换为实际域名
        resource '*',
          headers: :any,
          methods: [:get, :post, :put, :patch, :delete, :options, :head],
          credentials: true # 必须开启
      end
    end
    
  3. 统一OmniAuth Session配置
    避免重复设置session_store,将OmniAuth的session存储指向Rails的配置:

    # config/initializers/omniauth.rb
    OmniAuth.config.session_store = Rails.application.config.session_store
    
  4. 调整OAuth流程
    前端不要手动拼接code参数调用回调接口,正确流程是:

    • 用户点击登录跳转至Rails的/api/auth/google
    • Google授权完成后自动重定向到Rails回调地址
    • Rails处理授权逻辑后,重定向回前端并携带token

内容的提问来源于stack exchange,提问作者Anthony Renzo Ferrer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 10:33:14