JWT Authentication

Complete guide to programmatically creating sessions using JWT tokens in Authonomy

JWT Authentication Guide

Create secure user sessions programmatically using JSON Web Tokens (JWT). This guide covers JWT token creation, permissions system, and tenant configuration for seamless authentication integration.

Overview

Authonomy’s JWT authentication allows you to create authenticated sessions by generating and validating JWT tokens. This is ideal for:

  • API Integration: Programmatic user authentication
  • Single Sign-On: Custom authentication flows
  • Service-to-Service: Backend system integration
  • Mobile Apps: Secure token-based authentication

Authentication Flow

sequenceDiagram
    participant App as Your Application
    participant JWT as JWT Service
    participant Auth as Authonomy
    participant User as User Browser

    App->>JWT: Generate JWT Token
    JWT->>App: Signed JWT Token
    App->>User: Redirect with token
    User->>Auth: GET /auth/jwt?token=...
    Auth->>Auth: Validate & Create Session
    Auth->>User: Redirect to Application

JWT Token Structure

Required Claims

Every JWT token must include these standard and Authonomy-specific claims:

{
  // Standard JWT Claims
  "iss": "https://your-app.com",           // Issuer (must match config)
  "sub": "user-123",                       // Subject (user ID)
  "aud": "authonomy",                      // Audience 
  "exp": 1640995200,                       // Expiration timestamp
  "iat": 1640991600,                       // Issued at timestamp
  "jti": "token-unique-id",                // JWT ID (for one-time use)
  
  // Required Authonomy Claims
  "email": "user@example.com",             // User email address
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000", // Tenant UUID
  
  // Optional Claims
  "name": "John Doe",                      // User full name
  "roles": ["admin", "user"],              // User roles
  "permissions": ["read", "write"],        // Specific permissions
  "redirect_url": "https://app.com/dashboard", // Post-auth redirect
  "metadata": {                            // Custom user metadata
    "department": "Engineering",
    "employee_id": "E-1234"
  }
}

Permissions System

The permissions claim supports Authonomy’s resource-based access control:

{
  "permissions": [
    "users:read",                    // Read users
    "users:write",                   // Create/update users
    "applications:admin",            // Admin access to applications
    "reports:read:own",              // Read own reports only
    "settings:write:tenant:123"      // Write settings for specific tenant
  ]
}

Permission Format

Permissions follow the format: resource:action:scope:target

  • resource: What you’re accessing (users, applications, reports)
  • action: What you can do (read, write, admin, delete)
  • scope: Access level (own, team, tenant, global)
  • target: Specific resource ID (optional)

Creating JWT Tokens

Algorithm Support

Authonomy supports two signing algorithms:

Uses RSA public/private key pairs. More secure for distributed systems.

HS256 (Simple for Development)

Uses shared secret. Simpler setup but requires secret distribution.

Node.js Implementation

RS256 Example

const jwt = require('jsonwebtoken');
const fs = require('fs');

// Load your private key
const privateKey = fs.readFileSync('path/to/private-key.pem', 'utf8');

function createJWTToken(user, tenantId, options = {}) {
  const now = Math.floor(Date.now() / 1000);
  
  const payload = {
    // Standard claims
    iss: 'https://your-app.com',
    sub: user.id,
    aud: 'authonomy',
    exp: now + (options.expiresIn || 3600), // 1 hour default
    iat: now,
    jti: generateUniqueId(), // Implement your unique ID generation
    
    // Required Authonomy claims
    email: user.email,
    tenant_id: tenantId,
    
    // Optional claims
    name: user.name,
    roles: user.roles || [],
    permissions: user.permissions || [],
    redirect_url: options.redirectUrl,
    metadata: user.metadata || {}
  };
  
  const token = jwt.sign(payload, privateKey, {
    algorithm: 'RS256',
    keyid: 'your-key-id' // Must match JWKS
  });
  
  return token;
}

// Usage example
const user = {
  id: 'user-123',
  email: 'john@example.com',
  name: 'John Doe',
  roles: ['user'],
  permissions: ['users:read', 'reports:read:own']
};

const token = createJWTToken(user, 'tenant-uuid', {
  expiresIn: 7200, // 2 hours
  redirectUrl: 'https://your-app.com/dashboard'
});

console.log('Generated token:', token);

HS256 Example

const jwt = require('jsonwebtoken');

// Your shared secret (store securely!)
const secretKey = process.env.JWT_SECRET_KEY;

function createJWTToken(user, tenantId, options = {}) {
  const now = Math.floor(Date.now() / 1000);
  
  const payload = {
    iss: 'https://your-app.com',
    sub: user.id,
    aud: 'authonomy',
    exp: now + (options.expiresIn || 3600),
    iat: now,
    
    email: user.email,
    tenant_id: tenantId,
    name: user.name,
    roles: user.roles || [],
    permissions: user.permissions || []
  };
  
  const token = jwt.sign(payload, secretKey, {
    algorithm: 'HS256'
  });
  
  return token;
}

Python Implementation

RS256 Example

import jwt
import time
import uuid
from cryptography.hazmat.primitives import serialization

def load_private_key(key_path):
    with open(key_path, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None
        )
    return private_key

def create_jwt_token(user, tenant_id, private_key, **options):
    now = int(time.time())
    
    payload = {
        # Standard claims
        'iss': 'https://your-app.com',
        'sub': user['id'],
        'aud': 'authonomy',
        'exp': now + options.get('expires_in', 3600),
        'iat': now,
        'jti': str(uuid.uuid4()),
        
        # Required Authonomy claims
        'email': user['email'],
        'tenant_id': tenant_id,
        
        # Optional claims
        'name': user.get('name'),
        'roles': user.get('roles', []),
        'permissions': user.get('permissions', []),
        'redirect_url': options.get('redirect_url'),
        'metadata': user.get('metadata', {})
    }
    
    token = jwt.encode(
        payload,
        private_key,
        algorithm='RS256',
        headers={'kid': 'your-key-id'}
    )
    
    return token

# Usage example
private_key = load_private_key('path/to/private-key.pem')

user = {
    'id': 'user-123',
    'email': 'john@example.com',
    'name': 'John Doe',
    'roles': ['user'],
    'permissions': ['users:read', 'reports:read:own']
}

token = create_jwt_token(
    user, 
    'tenant-uuid', 
    private_key,
    expires_in=7200,
    redirect_url='https://your-app.com/dashboard'
)

print(f'Generated token: {token}')

HS256 Example

import jwt
import time
import os

def create_jwt_token(user, tenant_id, **options):
    secret_key = os.getenv('JWT_SECRET_KEY')
    now = int(time.time())
    
    payload = {
        'iss': 'https://your-app.com',
        'sub': user['id'],
        'aud': 'authonomy',
        'exp': now + options.get('expires_in', 3600),
        'iat': now,
        
        'email': user['email'],
        'tenant_id': tenant_id,
        'name': user.get('name'),
        'roles': user.get('roles', []),
        'permissions': user.get('permissions', [])
    }
    
    token = jwt.encode(payload, secret_key, algorithm='HS256')
    return token

Tenant Configuration

Before using JWT authentication, you need to configure your tenant’s JWT settings. This includes setting up validation rules, signing algorithms, required claims, and security policies.

Quick Configuration Steps

  1. Access JWT Settings: Navigate to Settings → Authentication → JWT Configuration in your admin dashboard
  2. Create Configuration: Set up issuer, algorithm (RS256/HS256), and validation rules
  3. Configure Security: Define required claims, session settings, and return URL policies
  4. Test Setup: Use the built-in token tester to verify your configuration

Configuration Requirements

Your JWT configuration must include:

  • Issuer: Must exactly match your JWT iss claim
  • Algorithm: RS256 (recommended) or HS256
  • Required Claims: At minimum email and tenant_id
  • Security Policies: Session timeout, one-time use enforcement, allowed domains

Using JWT Tokens

Browser Authentication

Redirect users to Authonomy with the JWT token:

function authenticateUser(token) {
  const authUrl = `https://auth.authonomy.io/auth/jwt?token=${encodeURIComponent(token)}`;
  
  // Redirect browser to Authonomy
  window.location.href = authUrl;
  
  // Authonomy will validate token and redirect back to your app
}

API Authentication

Use JWT tokens in API requests:

# Query parameter
curl "https://auth.authonomy.io/auth/jwt?token=YOUR_JWT_TOKEN"

# Authorization header  
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://auth.authonomy.io/auth/jwt"

# POST request body
curl -X POST "https://auth.authonomy.io/auth/jwt" \
  -H "Content-Type: application/json" \
  -d '{"token": "YOUR_JWT_TOKEN"}'

AJAX Authentication

For frontend JavaScript applications:

async function authenticateWithJWT(token) {
  try {
    const response = await fetch('https://auth.authonomy.io/auth/jwt', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({ token: token }),
      credentials: 'include' // Important: include cookies
    });
    
    if (response.ok) {
      const result = await response.json();
      console.log('Authentication successful:', result);
      
      // User is now authenticated
      // Session cookie is automatically set
      return result;
    } else {
      const error = await response.json();
      console.error('Authentication failed:', error);
      throw new Error(error.error || 'Authentication failed');
    }
  } catch (error) {
    console.error('JWT authentication error:', error);
    throw error;
  }
}

// Usage
authenticateWithJWT(jwtToken)
  .then(result => {
    // Redirect to dashboard or update UI
    window.location.href = '/dashboard';
  })
  .catch(error => {
    // Handle authentication error
    alert('Login failed: ' + error.message);
  });

JWKS Setup (RS256)

For RS256 algorithm, you need to provide a JWKS endpoint:

Generate Key Pair

# Generate private key
openssl genrsa -out jwt-private.pem 2048

# Extract public key
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem

# Get key components for JWKS
openssl rsa -in jwt-private.pem -noout -text

JWKS Endpoint

Create a JWKS endpoint at /.well-known/jwks.json:

const express = require('express');
const fs = require('fs');
const crypto = require('crypto');

const app = express();

// Load your public key
const publicKey = fs.readFileSync('jwt-public.pem', 'utf8');

app.get('/.well-known/jwks.json', (req, res) => {
  // Extract key components (implement based on your crypto library)
  const jwks = {
    keys: [
      {
        kid: 'your-key-id',
        kty: 'RSA',
        use: 'sig',
        alg: 'RS256',
        n: 'BASE64URL_ENCODED_MODULUS',
        e: 'AQAB' // Usually this value for RSA
      }
    ]
  };
  
  res.json(jwks);
});

Error Handling

Handle common JWT authentication errors:

async function handleJWTAuth(token) {
  try {
    const result = await authenticateWithJWT(token);
    return result;
  } catch (error) {
    switch (error.message) {
      case 'Invalid token':
        // Token is malformed or signature invalid
        console.error('JWT token is invalid');
        break;
        
      case 'Token expired':
        // Token has expired
        console.error('JWT token has expired');
        break;
        
      case 'Token has already been used':
        // One-time token was already used
        console.error('Token replay detected');
        break;
        
      case 'Configuration not found':
        // Issuer not configured in tenant
        console.error('JWT configuration missing');
        break;
        
      case 'Required claims validation failed':
        // Missing required claims
        console.error('Token missing required claims');
        break;
        
      default:
        console.error('JWT authentication error:', error.message);
    }
    
    // Redirect to login page or show error
    window.location.href = '/login?error=' + encodeURIComponent(error.message);
  }
}

Security Best Practices

Token Security

  1. Short Expiration: Keep token lifetimes short (15-60 minutes)
  2. HTTPS Only: Always use HTTPS for token transmission
  3. Secure Storage: Store private keys securely
  4. Key Rotation: Regularly rotate signing keys

Validation Settings

  1. Audience Validation: Always set and validate audience claim
  2. Issuer Validation: Strictly validate issuer claim
  3. One-Time Use: Enable for high-security scenarios
  4. Clock Skew: Account for clock differences between systems

Permissions Management

  1. Principle of Least Privilege: Grant minimal required permissions
  2. Regular Audits: Review and audit permission assignments
  3. Scoped Permissions: Use specific resource scoping
  4. Permission Validation: Validate permissions in your application

Testing JWT Configuration

Test your JWT setup before deployment:

Test Token Validation

curl -X POST "https://api.authonomy.io/v1/tenants/{tenant-id}/jwt-configs/{config-id}/test" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "YOUR_TEST_JWT_TOKEN"
  }'

Validate JWKS Endpoint

# Test JWKS accessibility
curl "https://your-app.com/.well-known/jwks.json"

# Should return valid JWKS format:
# {
#   "keys": [
#     {
#       "kid": "key-id",
#       "kty": "RSA", 
#       "use": "sig",
#       "alg": "RS256",
#       "n": "...",
#       "e": "AQAB"
#     }
#   ]
# }

Troubleshooting

Common Issues

Invalid Issuer

Error: “Configuration not found for issuer” Solution: Ensure JWT iss claim exactly matches configuration

Missing Claims

Error: “Required claims validation failed” Solution: Include all required claims in JWT payload

JWKS Issues

Error: “Failed to get JWKS” Solution: Verify JWKS URL is accessible and returns valid JSON

Clock Skew

Error: “Token is not valid yet” or “Token expired” Solution: Ensure server clocks are synchronized (use NTP)

Debug Mode

Enable debug logging to troubleshoot issues:

// Enable JWT debug logging
process.env.DEBUG = 'jwt:*';

// Or use Authonomy debug endpoint
const debugResponse = await fetch('/auth/jwt/debug?token=' + token);
console.log(await debugResponse.json());

Configuration Guides

API Documentation

Advanced Topics

Next Steps

  1. Configure your tenant using the JWT Configuration guide
  2. Implement token creation using the code examples above
  3. Test your integration with the built-in token tester
  4. Set up monitoring and error handling for production use

JWT authentication provides secure, flexible user authentication for modern applications. Start with tenant configuration, then implement token creation and validation in your application.