AgeOnce Docs
Integration

Node.js

AgeOnce integration with Node.js (Express, Fastify, Next.js)

Node.js Integration

Complete AgeOnce integration example with Node.js.

Install dependencies

npm install jsonwebtoken jwks-rsa

Configuration

// config.js
module.exports = {
  clientId: process.env.AGEONCE_CLIENT_ID,
  clientSecret: process.env.AGEONCE_CLIENT_SECRET,
  redirectUri: process.env.AGEONCE_REDIRECT_URI,
  apiUrl: process.env.AGEONCE_API_URL || 'https://app.ageonce.com',
};

Generate verification URL

const crypto = require('crypto');
const config = require('./config');

function generateVerifyUrl() {
  const state = crypto.randomBytes(16).toString('hex');
  
  const params = new URLSearchParams({
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    state: state,
  });
  
  return {
    url: `${config.apiUrl}/verify?${params.toString()}`,
    state: state, // Save in session for verification
  };
}

Exchange code for token

async function exchangeCodeForToken(code) {
  const response = await fetch(`${config.apiUrl}/api/oauth/token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      client_id: config.clientId,
      client_secret: config.clientSecret,
      code: code,
      redirect_uri: config.redirectUri,
    }),
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error_description || 'Token exchange failed');
  }
  
  return response.json();
}

Token validation

async function validateToken(token) {
  const response = await fetch(`${config.apiUrl}/api/oauth/validate`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ token }),
  });
  
  if (!response.ok) {
    return { valid: false };
  }
  
  const data = await response.json();
  return {
    valid: data.valid,
    payload: data.payload,
  };
}
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: `${config.apiUrl}/api/oauth/jwks`,
  cache: true,
  cacheMaxAge: 86400000, // 24 hours
});

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

async function validateTokenLocally(token) {
  return new Promise((resolve) => {
    jwt.verify(token, getKey, {
      algorithms: ['RS256'],
      issuer: 'ageonce',
    }, (err, decoded) => {
      if (err) {
        resolve({ valid: false, error: err.message });
      } else {
        resolve({ valid: true, payload: decoded });
      }
    });
  });
}

Express example

const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
}));

// Initiate verification
app.get('/verify', (req, res) => {
  const { url, state } = generateVerifyUrl();
  req.session.oauthState = state;
  res.redirect(url);
});

// Callback handler
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Verify state
  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state');
  }
  
  try {
    const { age_token } = await exchangeCodeForToken(code);
    const { valid, payload } = await validateToken(age_token);
    
    if (valid && payload.age_verified) {
      req.session.ageVerified = true;
      req.session.minAge = payload.min_age;
      res.redirect('/protected-content');
    } else {
      res.status(403).send('Age verification failed');
    }
  } catch (error) {
    res.status(500).send('Verification error');
  }
});

// Middleware for protected content
function requireAgeVerification(req, res, next) {
  if (req.session.ageVerified) {
    next();
  } else {
    res.redirect('/verify');
  }
}

app.get('/protected-content', requireAgeVerification, (req, res) => {
  res.send('Welcome to age-restricted content!');
});

app.listen(3000);

This example can be adapted for Fastify, Koa, Next.js API routes, and other Node.js frameworks.

On this page