faithfm / laravel-auth0-pattern
使用Auth0进行Laravel身份验证(为Faith FM项目开发)
Requires
- php: ^8.0
- faithfm/laravel-simple-auth-tokens: ^1.0.2
- faithfm/laravel-simple-auth0: ^1.0.2
- faithfm/laravel-simple-permissions: ^1.0.2
- guzzlehttp/guzzle: >7.0
- illuminate/support: ^9.0|^10.0
- laravel/framework: ^9.0|^10.0
- owen-it/laravel-auditing: >=12.0
README
一个针对Laravel身份验证(AuthN)和授权(AuthZ)有偏见的库/模式 - 设计用于提高我们的Faith FM Laravel/Vue项目之间的一致性。
我们的模式的大部分功能现在都已委托给 3个可重用包。这些子包意见较少,将对其他开发者更具广泛性
-
laravel-simple-auth0 - 使用 Auth0 的简单 会话 基础身份验证。
-
laravel-simple-auth-tokens - 使用 API密钥 的简单 令牌 基础身份验证(例如:
?api_token=XXXX
)。 -
laravel-simple-permissions - 使用 'user_permissions' 表/ Eloquent 模型进行简单 权限 / 授权网关。
在v4.0.0版本中,我们用我们自己的轻量级 laravel-simple-auth0 包替换了官方的 Auth0 Laravel SDK,并将其他功能拆分为可重用包。我们的 laravel-simple-auth0 使用Laravel内置的Session-Guard和一个使用由 简单Auth0 PHP流程 提供的 'sub' 标识符检索的Eloquent User模型。1
结构
我们的模式
- 导入3个可重用包。
- 发布了一系列意见模板,这些模板在我们应用程序中得到了重复使用。
- 提供配置和使用说明及指南。
本包(以及每个子包)提供的核心文件的结构是
安装和配置
提示
(更新 .env.example 模板也是一个好主意。有关Auth0配置等更多详细信息,请参阅 faithfm/laravel-simple-auth0 包)
AUTH0_DOMAIN=XXXX.xx.auth0.com AUTH0_CLIENT_ID=XXXXXXXXXXX AUTH0_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- 在config/auth.php中定义允许的权限列表
'defined_permissions' => [ 'use-app', // minimum permission to use the app 'admin-app', // master admin privilege // 'edit-catalog', // for catalog editors (assuming you're writing a catalogue application) ],
- 通过
user_permissions
表将权限分配给用户
注意
'限制'列是一个可选的JSON字段,可以用于定义对特权的特定限制/资格。例如:我们的媒体项目使用 'filter' 和 'fields' 来限制用户编辑特定的文件/字段。
工作原理
-
启用基于会话的身份验证的 /login、/logout、/callback 路由在 laravel-simple-auth0 中有文档说明。
-
基于令牌的身份验证
api_key=XXX
在 laravel-simple-auth-tokens 中有文档说明。 -
在config/auth.php中为所有
'defined_permissions'
自动创建的 授权网关 在 laravel-simple-permissions 中有文档说明。 -
我们的用户和用户权限模型集成了owen-it/laravel-auditing包,该包用于审计项目中模型的变化。(此包是composer依赖项。)
-
我们在开发过程中对认证+授权的理解(尤其是在Laravel + Auth0的背景下)已经在此记录,供参考。
用法
我们的模式使用非标准的认证守卫名称,以提高清晰度和消除歧义
-
'web'(Laravel默认)--> 'web_guard'(v2.2.0)--> 'ffm-session-guard'(v3.0.0)[基于SessionGuard驱动程序]
-
'api'(Laravel默认)--> 'api_guard'(v2.2.0)--> 'ffm-token-guard'(v3.0.0)[基于TokenGuard驱动程序]
认证示例
除了这些,我们的模式支持所有Laravel的正常认证(AuthN)方法,以检查是否登录,保护路由,获取用户等。
// Check a user is logged in - using a variety of methods (default guard is our session-based Auth0 guard) $loggedIn = auth()->check(); // using helper function (default guard) $loggedIn = Auth::check(); // using Facades (default guard) $loggedIn = auth('ffm-session-guard')->check(); // session-based guard specified - ie: using Auth0 $loggedIn = auth('ffm-token-guard')->check(); // token-based guard specified - ie: api_token=XXX $loggedIn = Auth::guard('ffm-token-guard')->check(); // token-based guard specified using Facades // Various useful guard functions - using the helper function and the default guard auth()->user() // Eloquent model for the logged-in User auth()->id() // User ID for this user - ie: user()->getAuthIdentifier() auth()->check() // TRUE if logged in auth()->guest() // FALSE if logged in auth()->authenticate() // throw AuthenticationException if not logged in (similiar to middleware checks) // Protect routes using 'auth' (Authenticate) middleware - throw AuthenticationException if not logged in Route::get(...) { ... } ->middleware('auth'); // using default guard ->middleware('auth:ffm-token-guard'); // specifying token-based guard ->middleware('auth:ffm-session-guard,ffm-token-guard'); // allow EITHER session-based OR token-based guards
当通过中间件(例如:上面的最后一个示例)指定了多个备用守卫时,该路由内的所有认证调用都隐式地使用找到的第一个已认证守卫:(中间件调用shouldUse()方法,该方法覆盖了配置的默认守卫。)
Route::get('/showuser', function () { return auth()->user(); // session-based or token-based guard will automatically be used depending on first authenticated guard found during middleware check })->middleware('auth:ffm-session-guard,ffm-token-guard');
我们还创建了一个扩展的auth_guards()
辅助函数,允许指定多个守卫,因为不幸的是,Laravel的guard()
辅助函数和Auth::guard()
外观都不支持中间件保护的路由之外的多个守卫
$user = auth()->user(); // default guard = SUPPORTED $user = auth('api')->user(); // specific guard = SUPPORTED $user = auth('web,api')->user(); // multiple guards = NOT SUPPORTED $user = auth_guards('web,api')->user(); // multiple guards = SUPPORTED (extended helper)
授权示例
使用自动为config/auth.php中所有'defined_permissions'
创建的授权网关检查权限(AuthZ)。
$allowed = Gate::allows('use-app'); // simple test $allowed = Gate::allows('use-app|edit-assets'); // ORed permissions (SPECIAL FEATURE) ...->middleware('can:use-app'); // protect in route definitions Gate::authorize('use-app|admin-app'); // protect elsewhere (ie: controllers) if (Gate::none(['use-app', 'admin-app'])) { // ditto - but manually aborting (instead of relying on "401 vs redirect" recommendation - see installation.md) abort(403); } @can('use-app') // blade templates
重要
注意可以使用特殊 '|' 字符在单个网关中测试多个(ORed)权限。这是对Laravel正常功能的扩展。
更复杂的限制字段检查/过滤目前仅在前端(见下一节)中实现...但与此同时,我们可能可以使用以下内容:(未经测试)
if (Gate::allows('use-app')) if (auth()->user()->permissions->restrictions['file'] == 'restrictedfile') // ALLOW/DENY STUFF FROM HAPPENING
扩展用法:Vue前端
LaravelUserPermissions.js
是一个辅助库,允许在前端执行权限检查。
此辅助函数假设用户权限是通过全局javascript LaravelAppGlobals
变量从后端传递到前端的(这通常是通过Blade文件传递的)。具体来说,它正在寻找全局LaravelAppGlobals.user.permissions
属性的存在。
简单的权限检查使用laravelUserCan()
函数
import { laravelUserCan } from "../LaravelUserPermissions"; if (laravelUserCan("use-app")) // ALLOW STUFF TO HAPPEN
更复杂的限制检查/过滤使用laravelUserRestrictions()
函数
import { laravelUserRestrictions } from "../LaravelUserPermissions"; const restrictions = laravelUserRestrictions("use-app"); if (restrictions.status == "NOT PERMITTED") // PREVENT STUFF FROM HAPPENING if (restrictions.status == "ALL PERMITTED") // UNFILTERED ACCESS if (restrictions.status == "SOME PERMITTED") { // PARTIAL/FILTERED ACCESS BASED ON RESTRICTIONS JSON DATA - IE: ASSUMING 'filter' field if (currentItem.startsWith(restrictions.filter) // DO STUFF IF FILTER ALLOWS
在上面的Media项目中的“限制”字段示例中,laravelUserRestrictions()
函数返回的“限制”对象将是
{ status: "SOME PERMITTED", fields: ["content","guests"], filter: "file:sa/*" }
状态字段的值将是
不允许
- 如果用户不存在请求的权限(例如:“使用应用程序”)。全部允许
- 如果请求的权限存在...并且“限制”字段为空。部分允许
- 如果请求的权限存在...并且“限制”字段包含有效的JSON数据。
其余字段(例如此示例中的字段和过滤)直接从数据库中的“限制”JSON数据中复制。
重要
提醒:根据良好的安全实践,您不应仅依赖前端检查来执行安全性,而应在后端也执行安全性检查。
通过 LaravelAppGlobals 向前端传递权限的示例代码
$LaravelAppGlobals = [ 'user' => auth()->user(), # THIS IS THE IMPORTANT ONE 'guest' => auth()->guest(), 'other-stuff' => $myStuff, ... ]; return view('media')->with('LaravelAppGlobals', $LaravelAppGlobals);
<!doctype html> <head> <!-- Scripts --> <script> var LaravelAppGlobals = Object.freeze({!! json_encode($LaravelAppGlobals) !!}); </script> ...
我们的应用程序是具有状态的“PHP Web 应用程序”(而不是无状态的“PHP 后端 API”,该 API 与使用 JWT 的 SPA 交互),我们没有使用到 Laravel SDK 中包含的许多高级功能——这往往会导致不必要的复杂性增加,并且在每次主要更新时重新集成都成为一个痛点。
一个重要的痛点是,Auth0 软件包的“用户提供者”默认不提供常规的 Eloquent 模型... 我们发现通过艰难的方式,许多软件包(包括 Laravel Nova)在用户提供者返回的内容不是预期的 Eloquent 模型时往往会崩溃。
Auth0 软件包的最新版本已经提高了提供真实 Eloquent 模型的兼容性,并移除了我们在早期版本中必须应用的一些黑客技巧,然而,在花费许多小时创建与 Auth0 软件包中改进的设计模式兼容的 v3.0.0 版本后,我们意识到我们的用户仓库仍然在已经复杂的解决方案之上构建了复杂的层级。
将这些复杂的层级与 Auth0 的PHP 快速入门的简洁性进行比较,我们的 laravel-simple-auth0 软件包现在仅使用 Auth0 来验证用户并提供唯一的 'sub' 标识符。然后使用这个标识符检索用户模型... 然后将其传递给 Laravel 内置的 SessionGuard... 在有效会话期间,该会话 guardian 会检索这个相同的用户模型以供后续的认证请求使用。