AgeOnce Docs
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/guzzle

Configuration

// 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.

On this page