关于 auth0-php-api
auth0-php-api 是一项技能,指导使用 auth0/auth0-php 的 API 模式验证 Auth0 访问令牌,以保护 PHP API 端点。内容涵盖令牌检查、可复用中间件、权限范围及端点测试,适用于 API,而非基于会话的 Web 应用。
主要功能
- 01以 API 模式配置 Auth0 SDK
- 02验证 JWT 签名及声明
- 03可复用的 Bearer 令牌守卫中间件
- 04为受保护路由检查权限范围
- 05测试公开及受保护端点
适用场景
- 01为 PHP API 路由添加 Auth0 令牌验证
- 02按已授予的权限范围限制端点
- 03在不暴露令牌的情况下测试公开和受保护路由
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ auth0/ agent-skills --skill auth0-php-api安装后在 Claude Code 中输入/auth0-php-api确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdintegration.mdsetup.md详细介绍
Auth0 PHP API 集成
在 API 模式(STRATEGY_API)下使用 auth0/auth0-php 验证 JWT 访问令牌,保护 PHP API 端点。
前置条件
- PHP 8.2+,并启用扩展:
mbstring、openssl、json - 已安装 Composer
- 已配置 Auth0 API 资源(必须是 API,而不是 Application)
- 如果尚未设置 Auth0,请先使用
auth0-quickstart技能
不适用场景
- 带有登录/注销流程的 PHP Web 应用 - 使用
auth0-php进行基于会话的身份验证 - Laravel 应用 - 使用内置 API guard 支持的
auth0/laravel-auth0 - Symfony 应用 - 使用带有安全 bundle 的
auth0/symfony - 单页应用 - 使用
auth0-react、auth0-vue或auth0-angular进行客户端身份验证 - 签发令牌 - 此技能用于验证访问令牌,而不是签发令牌
快速入门工作流
1. 安装 SDK
composer require auth0/auth0-php vlucas/phpdotenv guzzlehttp/guzzle guzzlehttp/psr7 "symfony/cache:^7.0"
auth0/auth0-php- Auth0 SDK(v8.x)vlucas/phpdotenv- 将.env文件加载到$_ENVguzzlehttp/guzzle+guzzlehttp/psr7- SDK 所需的 PSR-18 HTTP 客户端symfony/cache- 用于缓存 JWKS 密钥的 PSR-6 缓存(生产环境推荐)
2. 创建 Auth0 API
你需要在 Auth0 中创建一个 API(而不是 Application)。
停止 - 继续前先询问用户。
请原样提出以下问题,并等待用户回答后再进行其他操作:
"你希望如何创建 Auth0 API 资源?
- 自动 - 我将运行 Auth0 CLI 脚本来创建资源,并自动将准确的值写入
.env。- 手动 - 你在 Auth0 Dashboard 中自行创建 API(或通过
auth0 apis create创建),并向我提供 Domain 和 Audience。你更倾向哪种方式?(1 = 自动 / 2 = 手动)"
在用户回答前,不要继续任何设置步骤。不要默认选择手动方式。
如果用户选择自动方式,请按照设置指南中的完整 CLI 脚本操作。自动流程会为你写入 .env - 请跳过下方的第 3 步,直接继续第 4 步。
如果用户选择手动方式,请按照设置指南(手动设置部分)的完整说明操作。然后继续下方的第 3 步。
手动创建 API 的快速参考:
# Using Auth0 CLI
auth0 apis create \
--name "My PHP API" \
--identifier https://my-api.example.com \
--json
或者在 Auth0 Dashboard -> Applications -> APIs 中手动创建
3. 配置环境
创建 .env:
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_AUDIENCE=https://your-api.example.com
AUTH0_DOMAIN 是你的 Auth0 租户域名(不包含 https://)。AUTH0_AUDIENCE 是你在 Auth0 中创建 API 资源时设置的 API 标识符。
4. 在 API 模式下初始化 Auth0
创建 auth0.php 以初始化 SDK:
<?php
require 'vendor/autoload.php';
use Auth0\SDK\Auth0;
use Auth0\SDK\Configuration\SdkConfiguration;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$configuration = new SdkConfiguration(
strategy: SdkConfiguration::STRATEGY_API,
domain: $_ENV['AUTH0_DOMAIN'],
clientId: null,
audience: [$_ENV['AUTH0_AUDIENCE']],
tokenAlgorithm: 'RS256',
tokenCache: new FilesystemAdapter('auth0_jwks', 600, __DIR__ . '/var/cache'),
tokenCacheTtl: 600,
);
$auth0 = new Auth0($configuration);
与 Web 应用模式的主要区别:
STRATEGY_API- 无状态,不使用会话或 Cookie- RS256 验证不需要
clientId(仅 HS256 需要) audience接受允许的 audience 字符串数组tokenCache是用于缓存 JWKS 的 PSR-6CacheItemPoolInterface
5. 创建中间件函数
由于 SDK 不包含内置中间件,请创建一个可复用的守卫函数。创建 middleware.php:
<?php
use Auth0\SDK\Auth0;
use Auth0\SDK\Token;
use Auth0\SDK\Exception\InvalidTokenException;
function requireAuth(Auth0 $auth0, ?array $requiredScopes = null): array
{
$token = $auth0->getBearerToken(
server: ['HTTP_AUTHORIZATION']
);
if ($token === null) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'unauthorized', 'message' => 'Missing or invalid Bearer token']);
exit;
}
$claims = $token->toArray();
if ($requiredScopes !== null) {
$grantedScopes = isset($claims['scope']) ? explode(' ', $claims['scope']) : [];
$missingScopes = array_diff($requiredScopes, $grantedScopes);
if (!empty($missingScopes)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['error' => 'insufficient_scope', 'message' => 'Token lacks required scopes']);
exit;
}
}
return $claims;
}
getBearerToken() 会在你指定的位置查找 Bearer 令牌,根据 JWKS 端点验证签名,并验证声明(颁发者、audience、过期时间)。server 参数是要检查的 $_SERVER 键名数组(例如 ['HTTP_AUTHORIZATION']),而不是 $_SERVER 本身。成功时返回 TokenInterface;如果找不到有效令牌,则返回 null(不会抛出异常)。
6. 创建 API 路由
创建 index.php 作为前端控制器:
<?php
require 'auth0.php';
require 'middleware.php';
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
header('Content-Type: application/json');
switch ($path) {
case '/api/public':
echo json_encode(['message' => 'Public endpoint - no authentication required']);
break;
case '/api/private':
$claims = requireAuth($auth0);
echo json_encode(['message' => 'Private endpoint', 'sub' => $claims['sub']]);
break;
case '/api/private-scoped':
$claims = requireAuth($auth0, ['read:messages']);
echo json_encode(['messages' => [], 'sub' => $claims['sub']]);
break;
default:
http_response_code(404);
echo json_encode(['error' => 'not_found']);
break;
}
7. 访问令牌声明
解码后的 JWT 声明将以关联数组的形式返回:
$claims = requireAuth($auth0);
$userId = $claims['sub']; // user/client ID
$scopes = $claims['scope']; // space-separated granted scopes
$issuer = $claims['iss']; // issuer (your Auth0 domain URL)
$audience = $claims['aud']; // audience (string or array)
$expiration = $claims['exp']; // expiration timestamp
你也可以使用 Token 对象的类型化访问器方法:
$token = $auth0->getBearerToken(server: ['HTTP_AUTHORIZATION']);
if ($token !== null) {
$subject = $token->getSubject(); // returns ?string
$issuer = $token->getIssuer(); // returns ?string
$audience = $token->getAudience(); // returns ?array
$expiration = $token->getExpiration(); // returns ?int
}
8. 添加 CORS 标头
当 API 接收来自基于浏览器的 SPA 的请求时,请添加 CORS 标头。创建 cors.php:
<?php
function handleCors(array $allowedOrigins): void
{
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, Content-Type');
header('Access-Control-Max-Age: 86400');
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
}
然后在 index.php 顶部、现有的 require 'auth0.php' 行之前添加以下内容:
require 'cors.php';
handleCors(['https://your-spa-domain.com']);
更新后的 index.php 文件头应如下所示:
<?php
require 'cors.php';
handleCors(['https://your-spa-domain.com']);
require 'auth0.php';
require 'middleware.php';
// ... rest of index.php
必须先处理 CORS,再处理身份验证,这样预检 OPTIONS 请求就会直接返回,而不会触发令牌验证。
9. 测试 API
Agent 指令: 启动 PHP 内置服务器,并验证基本端点无需凭据即可正常工作:
php -S localhost:8000 index.php & sleep 1 curl -s http://localhost:8000/api/public curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/private
/api/public应返回{"message":"Public endpoint - no authentication required"}/api/private应返回 HTTP 401如果两项测试均通过,则中间件正常工作。完成后终止后台服务器:
kill %1 2>/dev/null失败检查: 如果服务器无法启动或
/api/public出错,请检查 PHP 语法问题或是否缺少.env。如果连续失败 5-6 次,请使用AskUserQuestion询问用户更多环境详情。
Agent 指令:停止——基本测试通过后询问用户。
公共端点和 401 测试通过后,询问用户:
“你的 API 已设置完成,中间件也正常工作(公共端点返回 200,私有端点在没有令牌时返回 401)。需要我帮你使用真实访问令牌进行测试吗?
- 需要——我会帮你配置 M2M 应用以获取测试令牌。
- 不需要——我暂时不需要帮助。
你更倾向于哪一种?”
除非用户回答需要,否则不要继续进行 M2M/令牌设置。如果用户回答不需要,请总结已完成的工作并停止。
Agent 指令(M2M 应用设置——仅在用户需要测试时执行):
如果用户选择设置测试,请询问:
“要测试受保护的端点,你需要一个获准为此 API 请求令牌的机器对机器(M2M)应用。
- 创建新应用——我会创建一个新的 M2M 应用,并授权它使用此 API。
- 使用现有应用——你已有一个 M2M 应用。请提供客户端 ID,我会授权它使用此 API。
你更倾向于哪一种?(1 = 创建新应用 / 2 = 使用现有应用)”
在用户回答前不要继续。不要擅自从租户中选择现有应用。
如果用户选择“创建新应用”:
auth0 apps create \ --name "${PWD##*/} (Test App)" \ --type m2m \ --no-input --json使用
jq解析 JSON 并提取client_id。不要使用--reveal-secrets——切勿在 Agent 上下文中暴露客户端密钥。 然后创建客户端授权:auth0 api post "client-grants" --data '{ "client_id": "<CLIENT_ID>", "audience": "<API_IDENTIFIER>", "scope": ["<SCOPES>"] }'如果用户选择“使用现有应用”: 询问客户端 ID。然后创建客户端授权,以授权该应用使用此 API:
auth0 api post "client-grants" --data '{ "client_id": "<USER_PROVIDED_CLIENT_ID>", "audience": "<API_IDENTIFIER>", "scope": ["<SCOPES>"] }'如果授权已存在(409 冲突),则无需处理——该应用已获授权。 Agent 指令(令牌隔离——至关重要):
Agent 绝不能直接查看或显示访问令牌值。令牌安全规则:
- 不要单独运行
auth0 test token——该命令会将令牌输出到标准输出- 不要要求用户将令牌粘贴到对话中
- 不要回显、打印或记录令牌值
- 不要将令牌存储在文件中
安全测试方法(单命令链):
如果用户明确要求测试受保护的端点,请使用单命令链,将令牌捕获到 shell 变量中并立即使用:
php -S localhost:8000 index.php & sleep 1 TEST_TOKEN=$(auth0 test token <M2M_CLIENT_ID> --audience <AUDIENCE> --scopes <SCOPE1,SCOPE2> 2>/dev/null | grep -o 'ey[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*') && \ [ -n "$TEST_TOKEN" ] && echo "Token acquired (${#TEST_TOKEN} chars)" && \ echo "=== GET /api/private ===" && \ curl -s http://localhost:8000/api/private -H "Authorization: Bearer $TEST_TOKEN" && \ echo "" && echo "=== GET /api/private-scoped ===" && \ curl -s http://localhost:8000/api/private-scoped -H "Authorization: Bearer $TEST_TOKEN" kill %1 2>/dev/null规则:
- 仅在用户明确要求测试时使用
- 始终在单个
&&命令中链接令牌获取与 curl- 绝不要添加
echo $TEST_TOKEN或任何会打印原始令牌值的命令- 如果令牌获取失败(变量为空),请说明 M2M 应用可能尚未获得授权
- 必须提供客户端 ID——如果尚未完成 M2M 设置,请先询问用户
如果用户没有要求测试,只需提供供他们手动运行的命令:
auth0 test token <CLIENT_ID> --audience <AUDIENCE> --scopes <SCOPE1,SCOPE2> curl http://localhost:8000/api/private -H "Authorization: Bearer <PASTE_TOKEN_HERE>"
启动服务器:
php -S localhost:8000 index.php
测试公共端点(无需令牌):
curl http://localhost:8000/api/public
在不提供令牌的情况下测试受保护端点(应返回 401):
curl http://localhost:8000/api/private
使用令牌测试受保护端点:
curl http://localhost:8000/api/private \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
测试带作用域的端点:
curl http://localhost:8000/api/private-scoped \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
通过 Auth0 Dashboard -> APIs -> Test 选项卡获取测试令牌,或使用上文所述的 M2M 流程获取。
常见错误
| 错误 | 修正方法 |
|---|---|
在源代码中硬编码 domain 或 audience | 始终从环境变量读取,切勿在代码中嵌入凭据 |
对 API 使用 STRATEGY_REGULAR | API 模式必须使用 SdkConfiguration::STRATEGY_API,它会禁用会话和 Cookie |
| 未安装 PSR-18 HTTP 客户端 | 必须安装 guzzlehttp/guzzle 或其他 PSR-18 客户端,否则 SDK 无法获取 JWKS |
| 未缓存 JWKS 密钥 | 如果没有 PSR-6 缓存,SDK 会在每次请求时获取 JWKS,务必配置 tokenCache |
将 audience 作为字符串传入 | audience 必须是数组:['https://my-api.example.com'],而不是 'https://my-api.example.com' |
将 domain 作为带 https:// 的完整 URL 传入 | domain 应为不带协议的域名,例如 my-tenant.us.auth0.com,而不是 https://my-tenant.us.auth0.com |
调用 decode() 时未指定令牌类型 | 手动调用 decode() 时,始终传入 tokenType: Token::TYPE_ACCESS_TOKEN |
| 将异常消息输出给用户 | 使用 error_log() 记录实际错误,并返回通用 JSON 错误消息 |
| 使用 ID 令牌而不是访问令牌 | API 身份验证必须使用访问令牌;ID 令牌供客户端应用使用 |
| 在 Auth0 中创建了 Application,而不是 API | 必须创建 API 资源(Applications -> APIs);Application 不会签发具有正确受众的访问令牌 |
设置 clientId 并认为 RS256 需要它 | 对于 RS256,clientId 是可选的,SDK 会通过 JWKS 端点进行验证 |
使用 clientSecret 验证 RS256 | clientSecret 仅适用于 HS256;RS256 使用 JWKS 中的公钥 |
将 $_SERVER 直接传给 getBearerToken() | server 参数接收要查找的键名数组,例如 ['HTTP_AUTHORIZATION'],而不是 $_SERVER 本身 |
主要 SDK 方法
| 方法 | 返回值 | 用途 |
|---|---|---|
getBearerToken | ?TokenInterface | 在指定的 $_SERVER 键中查找 Bearer 令牌,验证签名并校验声明。如果未找到令牌或验证失败,则返回 null(不会抛出异常)。 |
decode | TokenInterface | 手动解码并验证 JWT 字符串 |
configuration | SdkConfiguration | 访问 SDK 配置实例 |
Token::toArray | array | 将所有令牌声明作为关联数组返回 |
Token::getSubject | ?string | 返回 sub 声明(用户/客户端 ID) |
Token::getIssuer | ?string | 返回 iss 声明 |
Token::getAudience | ?array | 返回 aud 声明 |
Token::getExpiration | ?int | 返回 exp 声明(Unix 时间戳) |
相关技能
auth0-php- 用于通过基于会话的身份验证实现登录/登出的 PHP Web 应用auth0-quickstart- Auth0 基本设置和框架检测auth0-cli- 在终端中管理 Auth0 资源auth0-mfa- 添加多重身份验证
快速参考
适用于 API 的 SdkConfiguration:
$configuration = new SdkConfiguration(
strategy: SdkConfiguration::STRATEGY_API, // required - stateless mode
domain: $_ENV['AUTH0_DOMAIN'], // required
audience: [$_ENV['AUTH0_AUDIENCE']], // required - array of identifiers
tokenAlgorithm: 'RS256', // default
tokenCache: $psrCacheAdapter, // recommended for production
tokenCacheTtl: 600, // JWKS cache TTL in seconds
);
令牌验证:
$token = $auth0->getBearerToken(server: ['HTTP_AUTHORIZATION']); // returns ?TokenInterface
$claims = $token->toArray(); // all claims as array
$userId = $token->getSubject(); // sub claim
手动解码:
use Auth0\SDK\Token;
$token = $auth0->decode(
$jwtString,
tokenType: Token::TYPE_ACCESS_TOKEN,
);
环境变量:
AUTH0_DOMAIN- 你的 Auth0 租户域名(例如tenant.us.auth0.com)AUTH0_AUDIENCE- 你的 API 标识符(例如https://api.example.com)
常见用例:
- 保护路由 ->
requireAuth($auth0)(参见步骤 5) - 范围强制校验 ->
requireAuth($auth0, ['read:messages'])(参见步骤 5) - CORS 设置 -> 集成指南
- 多受众验证 -> 集成指南
- 高级配置 -> API 参考
详细文档
- 设置指南 - Auth0 CLI 设置、环境配置、获取测试令牌
- 集成指南 - 作用域、权限、中间件、多受众、CORS、错误处理
- API 参考 - API 模式的完整 SDK API、配置选项、令牌方法