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模式的特性,可从以下方向修复:
启用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修正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统一OmniAuth Session配置
避免重复设置session_store,将OmniAuth的session存储指向Rails的配置:# config/initializers/omniauth.rb OmniAuth.config.session_store = Rails.application.config.session_store调整OAuth流程
前端不要手动拼接code参数调用回调接口,正确流程是:- 用户点击登录跳转至Rails的
/api/auth/google - Google授权完成后自动重定向到Rails回调地址
- Rails处理授权逻辑后,重定向回前端并携带token
- 用户点击登录跳转至Rails的
内容的提问来源于stack exchange,提问作者Anthony Renzo Ferrer
相关产品推荐
相关产品推荐

