AgeOnce Docs

Security

Security best practices for AgeOnce integration

Security

Recommendations and best practices for secure AgeOnce integration.

Credential Protection

Client Secret

NEVER publish Client Secret in:

  • Frontend code (JavaScript, HTML)
  • Public repositories (GitHub, GitLab)
  • Client mobile applications
  • Logs or error messages

Proper storage:

# .env file (add to .gitignore!)
AGEONCE_CLIENT_ID=cl_abc123
AGEONCE_CLIENT_SECRET=cs_secret456
// Usage in code
const clientSecret = process.env.AGEONCE_CLIENT_SECRET;

Key Rotation

If you suspect Client Secret compromise:

  1. Immediately generate a new key in the Dashboard
  2. Update configuration on all servers
  3. The old key will be automatically deactivated

CSRF Protection

State Parameter

Always use the state parameter for CSRF attack protection:

// Generate
const state = crypto.randomBytes(32).toString('hex');
session.oauthState = state;

// Verify on callback
if (req.query.state !== session.oauthState) {
  throw new Error('CSRF attack detected');
}

Without state verification, an attacker can force a user to authorize on behalf of another account.

Token Validation

Required Checks

When validating age token, verify:

function validateToken(payload) {
  // 1. Verify issuer
  if (payload.iss !== 'ageonce') {
    throw new Error('Invalid issuer');
  }
  
  // 2. Verify expiration
  if (payload.exp < Date.now() / 1000) {
    throw new Error('Token expired');
  }
  
  // 3. Verify client_id
  if (payload.client_id !== process.env.AGEONCE_CLIENT_ID) {
    throw new Error('Token not issued for this client');
  }
  
  // 4. Verify verification
  if (!payload.age_verified) {
    throw new Error('Age not verified');
  }
  
  return true;
}

Cryptographic Validation

Tokens are signed with RS256. Verify the signature:

const jwt = require('jsonwebtoken');

// Get public key from JWKS
const publicKey = await getPublicKeyFromJWKS();

// Validation with signature verification
jwt.verify(token, publicKey, {
  algorithms: ['RS256'], // Important! Don't allow other algorithms
  issuer: 'ageonce',
});

Never disable signature verification! Without it, an attacker can forge a token.

HTTPS

Requirement

AgeOnce works only over HTTPS. HTTP requests will be rejected.

Ensure that:

  • Your site uses an SSL certificate
  • redirect_uri starts with https://
  • All API calls go to https://app.ageonce.com

HSTS

We recommend configuring HTTP Strict Transport Security:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Redirect URI

Validation

  • Register only exact URLs (no wildcards)
  • Avoid localhost in production
  • Don't use HTTP URLs

Correct:

https://app.example.com/callback
https://shop.example.com/ageonce/callback

Incorrect:

https://example.com/*
http://example.com/callback
https://localhost:3000/callback (development only)

Error Handling

Don't Expose Details

// Bad - exposes internal details
app.get('/callback', async (req, res) => {
  try {
    // ...
  } catch (error) {
    res.status(500).json({ error: error.message, stack: error.stack });
  }
});

// Good - generic message
app.get('/callback', async (req, res) => {
  try {
    // ...
  } catch (error) {
    console.error('Auth error:', error); // Log for yourself
    res.status(500).json({ error: 'Authentication failed' });
  }
});

Logging

What to Log

// Log events
logger.info('Age verification started', { clientId, timestamp });
logger.info('Age verification success', { clientId, minAge: 18 });
logger.warn('Age verification failed', { clientId, reason: 'expired_code' });

What NOT to Log

// Never log
logger.info('Token exchange', { 
  clientSecret, // NO!
  ageToken,     // NO! Contains personal data
  fullRequest   // NO! May contain secrets
});

Privacy

Data Minimization

AgeOnce is designed with data minimization principles:

  • sub is always "anonymous" — we don't identify users
  • Biometric data is not stored
  • We only know the fact of verification

GDPR

  • You are the controller of verification data
  • AgeOnce is the processor
  • See our Privacy Policy for details

Security Checklist

  • Client Secret is stored in environment variables
  • Client Secret is not in the repository
  • State parameter is generated and verified
  • Tokens are validated with signature verification
  • All URLs use HTTPS
  • Redirect URI is correctly registered
  • Errors don't expose internal details
  • Logs don't contain secrets or tokens

On this page