Skip to content

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.

Before you begin, ensure you have:

  1. PHP 8.2+ and Composer: Download from php.net
  2. AuthAction Account: You’ll need your AuthAction tenant domain and API identifier
Terminal window
composer require firebase/php-jwt vlucas/phpdotenv

Create a .env file in your project root:

Terminal window
AUTHACTION_DOMAIN=your-authaction-tenant-domain
AUTHACTION_AUDIENCE=your-authaction-api-identifier

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;
}
}

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']);
Terminal window
php -S localhost:8000 -t public

The API will be available at http://localhost:8000.

  1. Obtain an Access Token:
Terminal window
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"
}'
  1. Call the Public Endpoint:
Terminal window
curl http://localhost:8000/public
  1. Call the Protected Endpoint:
Terminal window
curl --request GET \
--url http://localhost:8000/protected \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  • Verify AUTHACTION_DOMAIN and AUTHACTION_AUDIENCE match your AuthAction dashboard exactly
  • Ensure the token is signed with the RS256 algorithm
  • Verify your application can reach https://your-authaction-tenant-domain/.well-known/jwks.json
  • Ensure allow_url_fopen is enabled in php.ini
  • Ensure the Authorization: Bearer <token> header is present and the token is not expired
  • Confirm the token’s audience matches your API identifier