AgeOnce Docs
API Reference

JWKS Endpoint

JSON Web Key Set for local token validation

JWKS Endpoint

Endpoint for retrieving public keys for local JWT token validation.

Endpoint

GET https://app.ageonce.com/api/oauth/jwks

Response

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "ageonce-public-key",
      "use": "sig",
      "alg": "RS256",
      "n": "base64url_encoded_modulus...",
      "e": "AQAB"
    }
  ]
}

Key fields

FieldDescription
ktyKey type (always "RSA")
kidKey identifier
usePurpose ("sig" for signing)
algAlgorithm (RS256)
nRSA modulus (base64url)
eRSA exponent (base64url)

Usage

Node.js (jwks-rsa)

const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');

const client = jwksClient({
  jwksUri: 'https://app.ageonce.com/api/oauth/jwks',
  cache: true,
  cacheMaxAge: 86400000, // Cache 24 hours
  rateLimit: true,
  jwksRequestsPerMinute: 10,
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

// Token validation
jwt.verify(token, getKey, {
  algorithms: ['RS256'],
  issuer: 'ageonce',
}, (err, decoded) => {
  if (err) {
    console.error('Token invalid:', err.message);
  } else {
    console.log('Token valid:', decoded);
  }
});

Python (PyJWT)

import jwt
import requests
from jwt.algorithms import RSAAlgorithm

# Fetch JWKS
jwks_response = requests.get('https://app.ageonce.com/api/oauth/jwks')
jwks = jwks_response.json()

# Convert JWK to PEM
key_data = jwks['keys'][0]
public_key = RSAAlgorithm.from_jwk(key_data)

# Validation
try:
    payload = jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],
        issuer='ageonce'
    )
    print('Token valid:', payload)
except jwt.InvalidTokenError as e:
    print('Token invalid:', str(e))

PHP (firebase/php-jwt)

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

// Fetch JWKS
$jwks = json_decode(
    file_get_contents('https://app.ageonce.com/api/oauth/jwks'),
    true
);

// Convert to keys
$keys = JWK::parseKeySet($jwks);

// Validation
try {
    $payload = JWT::decode($token, $keys);
    
    if ($payload->iss !== 'ageonce') {
        throw new Exception('Invalid issuer');
    }
    
    echo 'Token valid: ' . print_r($payload, true);
} catch (Exception $e) {
    echo 'Token invalid: ' . $e->getMessage();
}

Go

import (
    "github.com/golang-jwt/jwt/v5"
    "github.com/MicahParks/keyfunc/v2"
)

// Create JWKS client
jwks, err := keyfunc.Get("https://app.ageonce.com/api/oauth/jwks", keyfunc.Options{
    RefreshInterval: time.Hour * 24,
})
if err != nil {
    log.Fatal(err)
}

// Validation
token, err := jwt.Parse(tokenString, jwks.Keyfunc, jwt.WithValidMethods([]string{"RS256"}))
if err != nil {
    log.Println("Token invalid:", err)
} else if claims, ok := token.Claims.(jwt.MapClaims); ok {
    log.Println("Token valid:", claims)
}

Caching

Always cache JWKS! Making a request on every validation is inefficient.

Recommendations

ParameterRecommended value
Cache TTL24 hours
Max requests/min10
Retry on error3 attempts

Key updates

Keys are updated rarely (usually once a year). When updating:

  1. New key is added to JWKS
  2. Old key remains for transition period
  3. Tokens are signed with new key
  4. Old key is removed after a month

kid (Key ID) in JWT header indicates which key to use for validation.

Errors

HTTP codeDescription
200Success
500Internal server error

On this page