Protect Your API
Validate VoxKey JWT tokens in your backend to secure API endpoints.
Option 1: JWT validation (recommended)
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:
- Signature -- verify against the JWKS public keys
- Expiration (
exp) -- reject expired tokens - Issuer (
iss) -- must match your VoxKey realm URL - Audience (
aud) -- must match your API resource indicator - 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.