本文介绍如何利用 laravel sanctum 在物理隔离、不同代码仓库的两个 laravel 应用(app a 与 app b)之间安全共享用户会话与认证状态,支持跨域 token 验证与无状态登录。
本文介绍如何利用
laravel
sanctum 在物理隔离、不同代码仓库的两个 laravel 应用(app a 与 app b)之间安全共享用户会话与认证状态,支持跨域 token 验证与无状态登录。
Laravel Sanctum 原生设计支持两种主要认证模式:
Token-based API 认证
(适用于 SPA 或移动客户端)和
Cookie-based Web 认证
(适用于传统服务端渲染应用)。当两个 Laravel 应用需共享同一套用户体系并实现“一次登录、多端通行”时,推荐采用
Cookie + CSRF + Sanctum API Token 的混合方案
,前提是它们共享同一根域名(如 app-a.example.com 和 app-b.example.com)或可配置为信任的同源/跨域环境。
✅ 前提条件
两个应用均使用 Laravel ≥ 8.0 + Sanctum ≥ 2.0;
共享同一数据库(用户表共用),或通过 UserProvider 自定义同步逻辑(如 JWT 中继、OAuth2 中心化授权);
App A 作为认证中心(Auth Server),App B 作为受信客户端(Resource Client);
若跨子域,需统一配置 SESSION_DOMAIN=.example.com 和 SANCTUM_STATEFUL_DOMAINS=app-a.example.com,app-b.example.com。
? 关键配置步骤(以 App A 为认证源)
在 App A 中启用 Sanctum 并暴露登录接口
App B 配置 Sanctum 为 API 消费者
在 .env 中指定 App A 的 API 地址,并禁用本地 Session 登录(改用 Token 认证):
在 config/sanctum.php 中确保 stateful 匹配前端域名:
App B 登录逻辑(调用 App A 接口)
Laravel 13.2.0
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
下载
API 请求携带 Token(App B 调用自身受保护接口时)
⚠️ 注意事项
❌ 不要直接共享 SESSION_DRIVER=database 或 redis 实例来“复用 Session”——这违背无状态设计且存在安全与扩展风险;
✅ 推荐将 App A 作为唯一认证入口,App B 仅负责 Token 校验(Sanctum 默认支持 Bearer Token 解析);
? 生产环境务必启用 HTTPS,禁用 APP_DEBUG=true,并对 /api/login 加速率限制(throttle:api);
? 若两应用完全跨域(如 a.com ↔ b.net),则必须使用
Sanctum 的 Token 认证模式
(非 Cookie 模式),并通过 Authorization: Bearer {token} 头传递,此时 stateful 配置无效,需手动在中间件中校验。
✅ 总结
通过 Sanctum 的灵活 Token 机制,两个独立 Laravel 应用可安全、解耦地实现统一认证体验。核心在于:
App A 提供标准化登录接口并签发 Token;App B 消费该 Token 并交由 Sanctum 中间件统一验证
。无需引入 OAuth2 服务器等重型组件,即可达成轻量、可控、符合 Laravel 生态的最佳实践。
// routes/api.php (App A)
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
Route::post('/login', function (Request $request) {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$user = Auth::user();
$token = $user->createToken('app-b-access')->plainTextToken;
return response()->json(['token' => $token]);
}
return response()->json(['message' => 'Unauthorized'], 401);
});# .env (App B)
SANCTUM_STATEFUL_DOMAINS=app-b.example.com
APP_A_API_URL=https://app-a.example.com'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1,app-b.example.com')),// LoginController@authenticate (App B)
public function authenticate(Request $request)
{
$response = Http::asForm()->post(config('app.app_a_api_url').'/api/login', [
'email' => $request->email,
'password' => $request->password,
]);
if ($response->successful()) {
$token = $response->json()['token'];
// 将 token 存入本地 session 或 localStorage(SPA 场景)
session(['sanctum_token' => $token]);
return redirect()->intended('/dashboard');
}
return back()->withErrors(['email' => 'Invalid credentials']);
}// App B 中受保护路由示例
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user(); // 自动解析 token 并加载用户
});