Integrating with PHP API
This guide explains how to implement JWT authentication in a plain PHP API using AuthAction’s JWKS (JSON Web Key Set) endpoint. No framework required — runs with the PHP built-in server.
Example Repository: For a complete working example, check out our example repository.
Prerequisites
Section titled “Prerequisites”Before you begin, ensure you have:
- PHP 8.2+ and Composer: Download from php.net
- AuthAction Account: You’ll need your AuthAction tenant domain and API identifier
Configuration
Section titled “Configuration”1. Install Required Packages
Section titled “1. Install Required Packages”composer require firebase/php-jwt vlucas/phpdotenv2. Configure AuthAction Settings
Section titled “2. Configure AuthAction Settings”Create a .env file in your project root:
AUTHACTION_DOMAIN=your-authaction-tenant-domainAUTHACTION_AUDIENCE=your-authaction-api-identifier3. Implement JWT Validation
Section titled “3. Implement JWT Validation”Create src/JwtValidator.php:
<?php
namespace App;
use Firebase\JWT\ExpiredException;use Firebase\JWT\JWK;use Firebase\JWT\JWT;use UnexpectedValueException;
class JwtValidator{ private static ?array $jwksCache = null;
public static function verify(string $token): object { $domain = $_ENV['AUTHACTION_DOMAIN']; $audience = $_ENV['AUTHACTION_AUDIENCE']; $issuer = "https://{$domain}";
try { $keys = JWK::parseKeySet(self::getJwks($domain)); $payload = JWT::decode($token, $keys); } catch (ExpiredException) { throw new \RuntimeException('Token has expired'); } catch (UnexpectedValueException $e) { // kid not found — possible key rotation; bust cache and retry once if (str_contains($e->getMessage(), 'kid')) { self::$jwksCache = null; $keys = JWK::parseKeySet(self::getJwks($domain)); $payload = JWT::decode($token, $keys); } else { throw new \RuntimeException($e->getMessage()); } }
if (($payload->iss ?? '') !== $issuer) { throw new \RuntimeException('Invalid issuer'); }
$aud = isset($payload->aud) ? (array) $payload->aud : []; if (!in_array($audience, $aud, strict: true)) { throw new \RuntimeException('Invalid audience'); }
return $payload; }
private static function getJwks(string $domain): array { if (self::$jwksCache !== null) { return self::$jwksCache; }
$uri = "https://{$domain}/.well-known/jwks.json"; $response = file_get_contents($uri);
if ($response === false) { throw new \RuntimeException('Failed to fetch JWKS'); }
self::$jwksCache = json_decode($response, associative: true); return self::$jwksCache; }}1. Create the Entry Point
Section titled “1. Create the Entry Point”Create public/index.php:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\JwtValidator;
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');$dotenv->load();
header('Content-Type: application/json');
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET' && $path === '/public') { echo json_encode(['message' => 'This is a public message!']); exit;}
if ($method === 'GET' && $path === '/protected') { $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!str_starts_with($authHeader, 'Bearer ')) { http_response_code(401); echo json_encode(['error' => 'Missing or invalid Authorization header']); exit; }
$token = trim(substr($authHeader, 7));
try { $payload = JwtValidator::verify($token); echo json_encode([ 'message' => 'This is a protected message!', 'sub' => $payload->sub ?? null, ]); } catch (\RuntimeException $e) { http_response_code(401); echo json_encode(['error' => $e->getMessage()]); } exit;}
http_response_code(404);echo json_encode(['error' => 'Not found']);2. Start the Server
Section titled “2. Start the Server”php -S localhost:8000 -t publicThe API will be available at http://localhost:8000.
3. Testing the API
Section titled “3. Testing the API”- Obtain an Access Token:
curl --request POST \ --url https://your-authaction-tenant-domain/oauth2/m2m/token \ --header 'content-type: application/json' \ --data '{ "client_id": "your-authaction-m2m-app-clientid", "client_secret": "your-authaction-m2m-app-client-secret", "audience": "your-authaction-api-identifier", "grant_type": "client_credentials" }'- Call the Public Endpoint:
curl http://localhost:8000/public- Call the Protected Endpoint:
curl --request GET \ --url http://localhost:8000/protected \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'Common Issues
Section titled “Common Issues”Invalid Token Errors
Section titled “Invalid Token Errors”- Verify
AUTHACTION_DOMAINandAUTHACTION_AUDIENCEmatch your AuthAction dashboard exactly - Ensure the token is signed with the RS256 algorithm
Public Key Fetching Errors
Section titled “Public Key Fetching Errors”- Verify your application can reach
https://your-authaction-tenant-domain/.well-known/jwks.json - Ensure
allow_url_fopenis enabled inphp.ini
Unauthorized Access
Section titled “Unauthorized Access”- Ensure the
Authorization: Bearer <token>header is present and the token is not expired - Confirm the token’s audience matches your API identifier