Integration
PHP
AgeOnce integration with PHP (Laravel, Symfony, vanilla PHP)
PHP Integration
Complete AgeOnce integration example with PHP.
Install dependencies
composer require firebase/php-jwt guzzlehttp/guzzleConfiguration
// config.php
<?php
return [
'client_id' => getenv('AGEONCE_CLIENT_ID'),
'client_secret' => getenv('AGEONCE_CLIENT_SECRET'),
'redirect_uri' => getenv('AGEONCE_REDIRECT_URI'),
'api_url' => getenv('AGEONCE_API_URL') ?: 'https://app.ageonce.com',
];Helper class
// AgeOnce.php
<?php
class AgeOnce
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function generateVerifyUrl(): array
{
$state = bin2hex(random_bytes(16));
$params = http_build_query([
'client_id' => $this->config['client_id'],
'redirect_uri' => $this->config['redirect_uri'],
'state' => $state,
]);
return [
'url' => $this->config['api_url'] . '/verify?' . $params,
'state' => $state,
];
}
public function exchangeCodeForToken(string $code): array
{
$ch = curl_init($this->config['api_url'] . '/api/oauth/token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'client_id' => $this->config['client_id'],
'client_secret' => $this->config['client_secret'],
'code' => $code,
'redirect_uri' => $this->config['redirect_uri'],
]),
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($statusCode !== 200) {
throw new Exception($data['error_description'] ?? 'Token exchange failed');
}
return $data;
}
public function validateToken(string $token): array
{
$ch = curl_init($this->config['api_url'] . '/api/oauth/validate');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['token' => $token]),
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode !== 200) {
return ['valid' => false];
}
$data = json_decode($response, true);
return [
'valid' => $data['valid'] ?? false,
'payload' => $data['payload'] ?? null,
];
}
}Local JWT validation
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
class AgeOnceJwtValidator
{
private $config;
private static $cachedKeys = null;
public function __construct(array $config)
{
$this->config = $config;
}
private function getPublicKeys(): array
{
if (self::$cachedKeys !== null) {
return self::$cachedKeys;
}
$jwks = json_decode(
file_get_contents($this->config['api_url'] . '/api/oauth/jwks'),
true
);
self::$cachedKeys = JWK::parseKeySet($jwks);
return self::$cachedKeys;
}
public function validate(string $token): array
{
try {
$keys = $this->getPublicKeys();
$payload = JWT::decode($token, $keys);
// Verify issuer
if ($payload->iss !== 'ageonce') {
return ['valid' => false, 'error' => 'Invalid issuer'];
}
return [
'valid' => true,
'payload' => (array) $payload,
];
} catch (Exception $e) {
return ['valid' => false, 'error' => $e->getMessage()];
}
}
}Usage examples
<?php
session_start();
$config = require 'config.php';
$ageonce = new AgeOnce($config);
// verify.php - initiate verification
if ($_SERVER['REQUEST_URI'] === '/verify') {
$data = $ageonce->generateVerifyUrl();
$_SESSION['oauth_state'] = $data['state'];
header('Location: ' . $data['url']);
exit;
}
// callback.php - handle callback
if ($_SERVER['REQUEST_URI'] === '/callback') {
$code = $_GET['code'] ?? null;
$state = $_GET['state'] ?? null;
if ($state !== $_SESSION['oauth_state']) {
http_response_code(400);
echo 'Invalid state';
exit;
}
try {
$data = $ageonce->exchangeCodeForToken($code);
$result = $ageonce->validateToken($data['age_token']);
if ($result['valid'] && $result['payload']['age_verified']) {
$_SESSION['age_verified'] = true;
$_SESSION['min_age'] = $result['payload']['min_age'];
header('Location: /protected');
} else {
http_response_code(403);
echo 'Age verification failed';
}
} catch (Exception $e) {
http_response_code(500);
echo 'Error: ' . $e->getMessage();
}
exit;
}
// Middleware function
function requireAgeVerification()
{
if (empty($_SESSION['age_verified'])) {
header('Location: /verify');
exit;
}
}
// protected.php
requireAgeVerification();
echo 'Welcome to age-restricted content!';// routes/web.php
Route::get('/verify', [AgeOnceController::class, 'verify']);
Route::get('/callback', [AgeOnceController::class, 'callback']);
Route::get('/protected', [AgeOnceController::class, 'protected'])
->middleware('age.verified');
// app/Http/Controllers/AgeOnceController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\AgeOnce;
class AgeOnceController extends Controller
{
private $ageonce;
public function __construct(AgeOnce $ageonce)
{
$this->ageonce = $ageonce;
}
public function verify(Request $request)
{
$data = $this->ageonce->generateVerifyUrl();
$request->session()->put('oauth_state', $data['state']);
return redirect($data['url']);
}
public function callback(Request $request)
{
if ($request->state !== $request->session()->get('oauth_state')) {
abort(400, 'Invalid state');
}
try {
$data = $this->ageonce->exchangeCodeForToken($request->code);
$result = $this->ageonce->validateToken($data['age_token']);
if ($result['valid'] && $result['payload']['age_verified']) {
$request->session()->put('age_verified', true);
return redirect('/protected');
}
abort(403, 'Age verification failed');
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
}
public function protected()
{
return 'Welcome to age-restricted content!';
}
}
// app/Http/Middleware/AgeVerified.php
<?php
namespace App\Http\Middleware;
use Closure;
class AgeVerified
{
public function handle($request, Closure $next)
{
if (!$request->session()->get('age_verified')) {
return redirect('/verify');
}
return $next($request);
}
}// src/Controller/AgeOnceController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Service\AgeOnce;
class AgeOnceController extends AbstractController
{
#[Route('/verify', name: 'verify')]
public function verify(Request $request, AgeOnce $ageonce): Response
{
$data = $ageonce->generateVerifyUrl();
$request->getSession()->set('oauth_state', $data['state']);
return $this->redirect($data['url']);
}
#[Route('/callback', name: 'callback')]
public function callback(Request $request, AgeOnce $ageonce): Response
{
$code = $request->query->get('code');
$state = $request->query->get('state');
if ($state !== $request->getSession()->get('oauth_state')) {
throw $this->createAccessDeniedException('Invalid state');
}
try {
$data = $ageonce->exchangeCodeForToken($code);
$result = $ageonce->validateToken($data['age_token']);
if ($result['valid'] && $result['payload']['age_verified']) {
$request->getSession()->set('age_verified', true);
return $this->redirectToRoute('protected');
}
throw $this->createAccessDeniedException('Age verification failed');
} catch (\Exception $e) {
throw $this->createNotFoundException($e->getMessage());
}
}
#[Route('/protected', name: 'protected')]
public function protected(Request $request): Response
{
if (!$request->getSession()->get('age_verified')) {
return $this->redirectToRoute('verify');
}
return new Response('Welcome to age-restricted content!');
}
}For WordPress, use our ready-made plugin — WordPress integration.