auth0-php-api
Original:🇺🇸 English
Translated
Use when protecting PHP API endpoints with JWT Bearer token validation or scope checks. Integrates auth0/auth0-php in API mode for stateless APIs receiving access tokens.
10installs
Sourceauth0/agent-skills
Added on
NPX Install
npx skill4agent add auth0/agent-skills auth0-php-apiTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Auth0 PHP API Integration
Protect PHP API endpoints with JWT access token validation using in API mode ().
auth0/auth0-phpSTRATEGY_APIPrerequisites
- PHP 8.2+ with extensions: ,
mbstring,openssljson - Composer installed
- Auth0 API resource configured (not an Application - must be an API)
- If you don't have Auth0 set up yet, use the skill first
auth0-quickstart
When NOT to Use
- PHP web applications with login/logout flows - Use for session-based authentication
auth0-php - Laravel applications - Use which has built-in API guard support
auth0/laravel-auth0 - Symfony applications - Use with its security bundle
auth0/symfony - Single Page Applications - Use ,
auth0-react, orauth0-vuefor client-side authauth0-angular - Issuing tokens - This skill is for validating access tokens, not issuing them
Quick Start Workflow
1. Install SDK
bash
composer require auth0/auth0-php vlucas/phpdotenv guzzlehttp/guzzle guzzlehttp/psr7 "symfony/cache:^7.0"- - The Auth0 SDK (v8.x)
auth0/auth0-php - - Load
vlucas/phpdotenvfiles into.env$_ENV - +
guzzlehttp/guzzle- PSR-18 HTTP client required by the SDKguzzlehttp/psr7 - - PSR-6 cache for JWKS key caching (recommended for production)
symfony/cache
2. Create Auth0 API
You need an API (not Application) in Auth0.
STOP - ask the user before proceeding.Ask exactly this question and wait for their answer before doing anything else:"How would you like to create the Auth0 API resource?
- Automated - I'll run Auth0 CLI scripts that create the resource and write the exact values to your
automatically..env- Manual - You create the API yourself in the Auth0 Dashboard (or via
) and provide me the Domain and Audience.auth0 apis createWhich do you prefer? (1 = Automated / 2 = Manual)"Do NOT proceed to any setup steps until the user has answered. Do NOT default to manual.
If the user chose Automated, follow the Setup Guide for complete CLI scripts. The automated path writes for you - skip Step 3 below and proceed directly to Step 4.
.envIf the user chose Manual, follow the Setup Guide (Manual Setup section) for full instructions. Then continue with Step 3 below.
Quick reference for manual API creation:
bash
# Using Auth0 CLI
auth0 apis create \
--name "My PHP API" \
--identifier https://my-api.example.com \
--jsonOr create manually in Auth0 Dashboard -> Applications -> APIs
3. Configure Environment
Create :
.envbash
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_AUDIENCE=https://your-api.example.comAUTH0_DOMAINhttps://AUTH0_AUDIENCE4. Initialize Auth0 in API Mode
Create to initialize the SDK:
auth0.phpphp
<?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);Key differences from web app mode:
- - stateless, no sessions or cookies
STRATEGY_API - is not required for RS256 validation (only needed for HS256)
clientId - accepts an array of allowed audience strings
audience - is a PSR-6
tokenCachefor JWKS cachingCacheItemPoolInterface
5. Create Middleware Function
Since the SDK does not include a built-in middleware, create a reusable guard function. Create :
middleware.phpphp
<?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()server$_SERVER['HTTP_AUTHORIZATION']$_SERVERTokenInterfacenull6. Create API Routes
Create as a front controller:
index.phpphp
<?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. Access Token Claims
The decoded JWT claims are returned as an associative array:
php
$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 timestampYou can also use the object's typed accessor methods:
Tokenphp
$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. Add CORS Headers
When your API receives requests from a browser-based SPA, add CORS headers. Create :
cors.phpphp
<?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;
}
}Then add these lines at the top of , before the existing line:
index.phprequire 'auth0.php'php
require 'cors.php';
handleCors(['https://your-spa-domain.com']);The updated head should be:
index.phpphp
<?php
require 'cors.php';
handleCors(['https://your-spa-domain.com']);
require 'auth0.php';
require 'middleware.php';
// ... rest of index.phpCORS must be handled before auth so that preflight requests short-circuit without triggering token validation.
OPTIONS9. Test the API
Agent instruction: Start the PHP built-in server and verify the basic endpoints work without credentials:bashphp -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
should return/api/public{"message":"Public endpoint - no authentication required"} should return HTTP 401/api/privateIf both pass, the middleware is working. Kill the background server when done:bashkill %1 2>/dev/nullFailcheck: If the server fails to start orerrors, check for PHP syntax issues or missing/api/public. After 5-6 failed iterations, use.envto ask the user for more details about their environment.AskUserQuestion
Agent instruction: STOP - ask after basic tests pass.Once public/401 tests pass, ask the user:"Your API is set up and the middleware is working (public returns 200, private returns 401 without a token). Would you like me to help you set up testing with a real access token?
- Yes - I'll help you configure an M2M application to get test tokens.
- No - I'm done for now.
Which do you prefer?"Do NOT proceed with M2M/token setup unless the user says yes. If they say no, summarize what was done and stop.
Agent instruction (M2M app setup - only if user wants testing):If the user chose to set up testing, ask:"To test your protected endpoints, you need a Machine-to-Machine (M2M) application authorized to request tokens for this API.
- Create new - I'll create a new M2M application and authorize it for this API.
- Use existing - You already have an M2M application. Provide the Client ID and I'll authorize it for this API.
Which do you prefer? (1 = Create new / 2 = Use existing)"Do NOT proceed until the user answers. Do NOT silently pick an existing application from the tenant.If the user chose "Create new":bashauth0 apps create \ --name "${PWD##*/} (Test App)" \ --type m2m \ --no-input --jsonParse the JSON withto extractjq. Do NOT useclient_id- never expose client secrets in agent context. Then create a client grant:--reveal-secretsbashauth0 api post "client-grants" --data '{ "client_id": "<CLIENT_ID>", "audience": "<API_IDENTIFIER>", "scope": ["<SCOPES>"] }'If the user chose "Use existing": Ask for the Client ID. Then create a client grant to authorize it for this API:bashauth0 api post "client-grants" --data '{ "client_id": "<USER_PROVIDED_CLIENT_ID>", "audience": "<API_IDENTIFIER>", "scope": ["<SCOPES>"] }'If the grant already exists (409 conflict), that's fine - the app is already authorized.
Agent instruction (TOKEN ISOLATION - CRITICAL):The agent MUST NEVER directly see or display access token values. Token security rules:
- Do NOT run
on its own - it outputs the token to stdoutauth0 test token- Do NOT ask the user to paste their token into the conversation
- Do NOT echo, print, or log the token value
- Do NOT store the token in a file
Secure testing approach (single-command chain):If the user explicitly asks to test the protected endpoints, use a single-command chain that captures the token into a shell variable and immediately uses it:bashphp -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/nullRules:
- ONLY use when the user explicitly asks to test
- Always chain token acquisition + curl in a SINGLE
command&&- NEVER add
or any command that would print the raw token valueecho $TEST_TOKEN- If the token acquisition fails (empty variable), report that the M2M app may not be authorized
- Client ID is REQUIRED - if M2M setup was not completed, ask the user first
If the user does NOT ask to test, just provide the commands for them to run manually:auth0 test token <CLIENT_ID> --audience <AUDIENCE> --scopes <SCOPE1,SCOPE2> curl http://localhost:8000/api/private -H "Authorization: Bearer <PASTE_TOKEN_HERE>"
Start the server:
bash
php -S localhost:8000 index.phpTest public endpoint (no token needed):
bash
curl http://localhost:8000/api/publicTest protected endpoint without token (should return 401):
bash
curl http://localhost:8000/api/privateTest protected endpoint with token:
bash
curl http://localhost:8000/api/private \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Test scoped endpoint:
bash
curl http://localhost:8000/api/private-scoped \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Get a test token via Auth0 Dashboard -> APIs -> Test tab, or via the M2M flow described above.
Common Mistakes
| Mistake | Fix |
|---|---|
Hardcoding | Always read from environment variables - never embed credentials in code |
Using | API mode must use |
| Installing without a PSR-18 HTTP client | Must have |
| Not caching JWKS keys | Without a PSR-6 cache, the SDK fetches JWKS on every request - always configure |
Passing | |
Passing | |
Using | Always pass |
| Echoing exception messages to users | Use |
| Using an ID token instead of an access token | Must use the access token for API auth - ID tokens are for the client app |
| Created an Application instead of an API in Auth0 | Must create an API resource (Applications -> APIs) - an Application doesn't issue access tokens with the right audience |
Setting | For RS256, |
Using | |
Passing | The |
Key SDK Methods
| Method | Returns | Purpose |
|---|---|---|
| | Searches specified |
| | Manually decodes and validates a JWT string |
| | Access the SDK configuration instance |
| | Returns all token claims as an associative array |
| | Returns the |
| | Returns the |
| | Returns the |
| | Returns the |
Related Skills
- - For PHP web apps with login/logout using session-based auth
auth0-php - - Basic Auth0 setup and framework detection
auth0-quickstart - - Manage Auth0 resources from the terminal
auth0-cli - - Add Multi-Factor Authentication
auth0-mfa
Quick Reference
SdkConfiguration for APIs:
php
$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 validation:
php
$token = $auth0->getBearerToken(server: ['HTTP_AUTHORIZATION']); // returns ?TokenInterface
$claims = $token->toArray(); // all claims as array
$userId = $token->getSubject(); // sub claimManual decode:
php
use Auth0\SDK\Token;
$token = $auth0->decode(
$jwtString,
tokenType: Token::TYPE_ACCESS_TOKEN,
);Environment variables:
- - your Auth0 tenant domain (e.g.
AUTH0_DOMAIN)tenant.us.auth0.com - - your API identifier (e.g.
AUTH0_AUDIENCE)https://api.example.com
Common Use Cases:
- Protect routes -> (see Step 5)
requireAuth($auth0) - Scope enforcement -> (see Step 5)
requireAuth($auth0, ['read:messages']) - CORS setup -> Integration Guide
- Multi-audience validation -> Integration Guide
- Advanced configuration -> API Reference
Detailed Documentation
- Setup Guide - Auth0 CLI setup, environment configuration, getting test tokens
- Integration Guide - Scopes, permissions, middleware, multi-audience, CORS, error handling
- API Reference - Complete SDK API for API mode, configuration options, token methods