Skip to main content

Protect Your API

Validate VoxKey JWT tokens in your backend to secure API endpoints.

Validate tokens locally using the realm's public keys. No network call required.

Get the JWKS

curl https://your-domain.com/oauth2/{realmUUID}/oidc/jwks

Node.js (Express)

import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

const client = jwksClient({
jwksUri: 'https://your-domain.com/oauth2/{realmUUID}/oidc/jwks',
cache: true,
rateLimit: true,
});

function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
callback(err, key?.getPublicKey());
});
}

function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'No token' });

jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
if (err) return res.status(401).json({ error: 'Invalid token' });
req.user = decoded;
next();
});
}

// Check scopes
function requireScope(scope) {
return (req, res, next) => {
const scopes = req.user.scope?.split(' ') || [];
if (!scopes.includes(scope)) {
return res.status(403).json({ error: 'Insufficient scope' });
}
next();
};
}

// Usage
app.get('/api/posts', authMiddleware, requireScope('read:posts'), (req, res) => {
// req.user contains the decoded JWT
});

PHP (Laravel)

use Firebase\JWT\JWT;
use Firebase\JWT\JWK;

class VoxKeyMiddleware
{
private static ?array $jwks = null;

public function handle($request, Closure $next, string $scope = null)
{
$token = $request->bearerToken();
if (!$token) {
return response()->json(['error' => 'No token'], 401);
}

try {
$keys = $this->getJwks();
$decoded = JWT::decode($token, JWK::parseKeySet($keys));
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid token'], 401);
}

if ($scope) {
$scopes = explode(' ', $decoded->scope ?? '');
if (!in_array($scope, $scopes)) {
return response()->json(['error' => 'Insufficient scope'], 403);
}
}

$request->attributes->set('jwt_user', $decoded);
return $next($request);
}

private function getJwks(): array
{
if (self::$jwks) return self::$jwks;

$response = file_get_contents(
'https://your-domain.com/oauth2/{realmUUID}/oidc/jwks'
);
self::$jwks = json_decode($response, true);
return self::$jwks;
}
}

// In routes
Route::get('/posts', [PostController::class, 'index'])
->middleware('voxkey:read:posts');

Option 2: Token Introspection

Use the introspection endpoint (RFC 7662) to check token validity server-side. Useful when you need real-time revocation checks.

curl -X POST https://your-domain.com/oauth2/{realmUUID}/introspect \
-d token=ACCESS_TOKEN \
-u CLIENT_ID:CLIENT_SECRET

Response:

{
"active": true,
"sub": "user_abc123",
"client_id": "your-app",
"scope": "openid profile read:posts",
"exp": 1711612800,
"iat": 1711609200
}

If the token is revoked or expired, active will be false.

Token validation checklist

When validating JWTs, always check:

  1. Signature -- verify against the JWKS public keys
  2. Expiration (exp) -- reject expired tokens
  3. Issuer (iss) -- must match your VoxKey realm URL
  4. Audience (aud) -- must match your API resource indicator
  5. Scopes (scope) -- check the user has the required permission

Token Revocation

Revoke tokens using RFC 7009:

curl -X POST https://your-domain.com/oauth2/{realmUUID}/revoke \
-d token=REFRESH_TOKEN \
-d token_type_hint=refresh_token \
-u CLIENT_ID:CLIENT_SECRET

M2M (Machine-to-Machine) tokens

For backend services, use the Client Credentials grant:

curl -X POST https://your-domain.com/oauth2/{realmUUID}/token \
-d grant_type=client_credentials \
-d client_id=SERVICE_CLIENT_ID \
-d client_secret=SERVICE_SECRET \
-d scope="users:read users:write" \
-d resource=https://your-domain.com/api/v1/{realmUUID}

The returned token can be validated the same way as user tokens.