Skip to content

HeliosDB JWT Authentication Configuration Guide

HeliosDB JWT Authentication Configuration Guide

Overview

HeliosDB uses industry-standard JWT (JSON Web Tokens) for API authentication across all REST, GraphQL, and HTTP Gateway endpoints. This guide provides comprehensive configuration instructions for development, staging, and production environments.


Table of Contents

  1. Quick Start
  2. JWT Architecture
  3. Configuration
  4. Token Lifecycle
  5. Security Best Practices
  6. API Integration
  7. Troubleshooting
  8. Advanced Topics

Quick Start

Generate and Validate a Token

use heliosdb_security::jwt::{JwtValidator, JwtConfig};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create validator with default configuration
let validator = JwtValidator::new(JwtConfig::default())?;
// Issue a token
let token = validator.issue_token(
"user123", // User ID
vec!["read".to_string()], // Permissions
HashMap::new(), // Metadata
)?;
println!("Token: {}", token);
// Validate the token
let claims = validator.validate(&token).await?;
println!("User: {}", claims.sub);
println!("Permissions: {:?}", claims.permissions);
Ok(())
}

Using JWT in HTTP Requests

Terminal window
# Obtain a token (implementation-specific)
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9..."
# Make authenticated request
curl -H "Authorization: Bearer $TOKEN" \
https://heliosdb.example.com/snowflake/queries

JWT Architecture

Token Structure

HeliosDB JWTs conform to RFC 7519 and consist of three parts:

HEADER.PAYLOAD.SIGNATURE

Example Token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.
eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzMxMzUwNDAwLCJpYXQiOjE3MzEzNDY4MDAsIm5iZiI6MTczMTM0NjgwMCwiYXVkIjoiaGVsaW9zZGItYXBpIiwiaXNzIjoiaGVsaW9zZGIiLCJqdGkiOiI1NTBlOTcyNi1hNGQ4LTQyZGQtYTQ0MC00NWJhOTY3OWE2ZmUiLCJwZXJtaXNzaW9ucyI6WyJyZWFkIiwid3JpdGUiXSwic2Vzc2lvbl9pZCI6IjdhMmJjZDEyLTM0NTYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MCIsIm1ldGFkYXRhIjp7fX0.
[RS256_SIGNATURE]

Decoded Structure

Header:

{
"alg": "RS256", // Algorithm
"typ": "JWT", // Type
"kid": "abc123" // Key ID for rotation
}

Payload (Claims):

{
"sub": "user123", // Subject (user ID)
"exp": 1731350400, // Expiration time
"iat": 1731346800, // Issued at
"nbf": 1731346800, // Not before
"aud": "heliosdb-api", // Audience
"iss": "heliosdb", // Issuer
"jti": "550e9726-a4d8-42dd-a440-45ba9679a6fe", // JWT ID (unique)
"permissions": ["read", "write"], // Custom permissions
"session_id": "7a2bcd12-3456-7890-abcd-...", // Session tracking
"metadata": { // Custom metadata
"department": "engineering",
"role": "senior"
}
}

Signature

The signature is computed using RS256 (RSA-SHA256) by default:

RSASSA-PKCS1-v1_5(
SHA256(base64url(header) + "." + base64url(payload)),
private_key
)

Configuration

Default Configuration

use heliosdb_security::jwt::JwtConfig;
use chrono::Duration;
use jsonwebtoken::Algorithm;
let config = JwtConfig {
// Required issuer (validates 'iss' claim)
issuer: Some("heliosdb".to_string()),
// Required audience (validates 'aud' claim)
audience: Some("heliosdb-api".to_string()),
// Allowed signature algorithms
algorithms: vec![Algorithm::RS256],
// Clock skew tolerance (seconds)
leeway: 60,
// Access token lifetime
token_lifetime: Duration::hours(1),
// Refresh token lifetime
refresh_lifetime: Duration::days(7),
// Enable rate limiting
rate_limit_enabled: true,
// Max validations per second
rate_limit_per_second: 100,
};

Environment-Specific Configuration

Development

let dev_config = JwtConfig {
issuer: Some("heliosdb-dev".to_string()),
audience: Some("heliosdb-api-dev".to_string()),
algorithms: vec![Algorithm::RS256, Algorithm::ES256],
leeway: 120, // More lenient
token_lifetime: Duration::hours(24), // Long-lived for testing
refresh_lifetime: Duration::days(30),
rate_limit_enabled: false, // Disabled for testing
rate_limit_per_second: 1000,
};

Staging

let staging_config = JwtConfig {
issuer: Some("heliosdb-staging".to_string()),
audience: Some("heliosdb-api-staging".to_string()),
algorithms: vec![Algorithm::RS256],
leeway: 60,
token_lifetime: Duration::hours(1),
refresh_lifetime: Duration::days(7),
rate_limit_enabled: true,
rate_limit_per_second: 100,
};

Production

let prod_config = JwtConfig {
issuer: Some("heliosdb-production".to_string()),
audience: Some("heliosdb-api".to_string()),
algorithms: vec![Algorithm::RS256], // Only RS256
leeway: 30, // Strict clock tolerance
token_lifetime: Duration::minutes(15), // Short-lived
refresh_lifetime: Duration::days(1), // Short refresh window
rate_limit_enabled: true,
rate_limit_per_second: 50, // Strict rate limiting
};

Configuration from Environment Variables

use std::env;
use chrono::Duration;
fn load_jwt_config() -> anyhow::Result<JwtConfig> {
Ok(JwtConfig {
issuer: env::var("JWT_ISSUER").ok(),
audience: env::var("JWT_AUDIENCE").ok(),
algorithms: vec![Algorithm::RS256],
leeway: env::var("JWT_LEEWAY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60),
token_lifetime: Duration::seconds(
env::var("JWT_TOKEN_LIFETIME_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(3600)
),
refresh_lifetime: Duration::seconds(
env::var("JWT_REFRESH_LIFETIME_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(604800)
),
rate_limit_enabled: env::var("JWT_RATE_LIMIT_ENABLED")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(true),
rate_limit_per_second: env::var("JWT_RATE_LIMIT_PER_SEC")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(100),
})
}

Environment Variables:

Terminal window
# .env file
JWT_ISSUER=heliosdb-production
JWT_AUDIENCE=heliosdb-api
JWT_LEEWAY=30
JWT_TOKEN_LIFETIME_SECS=900 # 15 minutes
JWT_REFRESH_LIFETIME_SECS=86400 # 1 day
JWT_RATE_LIMIT_ENABLED=true
JWT_RATE_LIMIT_PER_SEC=50

Token Lifecycle

1. Token Issuance

use heliosdb_security::jwt::JwtValidator;
use std::collections::HashMap;
let validator = JwtValidator::new(config)?;
// Issue access token
let access_token = validator.issue_token(
"user123",
vec!["read".to_string(), "write".to_string()],
{
let mut metadata = HashMap::new();
metadata.insert("department".to_string(), "engineering".to_string());
metadata
},
)?;
// Issue refresh token
let refresh_token = validator.issue_refresh_token("user123")?;

2. Token Validation

// Validate token (async)
let claims = validator.validate(&access_token).await?;
// Synchronous validation (for non-async contexts)
let claims = validator.validate_sync(&access_token)?;
// Extract user information
println!("User ID: {}", claims.sub);
println!("Permissions: {:?}", claims.permissions);
println!("Session ID: {:?}", claims.session_id);

3. Token Refresh

// Use refresh token to get new access token
let new_access_token = validator.refresh_token(&refresh_token).await?;
// Refresh token contains special "refresh" permission
// and is only valid for refreshing, not API access

4. Token Revocation

// Revoke a token (adds to revocation list)
validator.revoke_token(&access_token).await?;
// Token will fail validation after revocation
match validator.validate(&access_token).await {
Err(e) if e.to_string().contains("revoked") => {
println!("Token has been revoked");
}
_ => {}
}

5. Revocation Cleanup

// Clean up expired revoked tokens (periodic maintenance)
let removed = validator.cleanup_revocations().await?;
println!("Cleaned up {} expired revocations", removed);

Security Best Practices

1. Key Management

Generate RSA Keys

use heliosdb_security::jwt::JwtKeyPair;
// Generate new RSA key pair (2048-bit)
let key_pair = JwtKeyPair::generate_rsa()?;
// Export for storage
println!("Key ID: {}", key_pair.kid);
println!("Algorithm: {:?}", key_pair.algorithm);

Load Keys from PEM

use std::fs;
let private_key_pem = fs::read("/path/to/private_key.pem")?;
let key_pair = JwtKeyPair::from_rsa_pem(&private_key_pem)?;

Key Rotation

// Generate new key
let new_key = JwtKeyPair::generate_rsa()?;
// Add to validator (old keys remain valid)
validator.add_key(new_key.clone())?;
// Switch to new key for signing (rotation complete)
validator.rotate_key(new_key)?;
// Clean up old keys (keep last 3)
let removed = validator.cleanup_old_keys(3)?;
println!("Removed {} old keys", removed);

2. Token Storage

DO:

  • Store access tokens in memory (JavaScript variables)
  • Use httpOnly cookies for refresh tokens
  • Clear tokens on logout
  • Implement token refresh logic

DON’T:

  • ❌ Store tokens in localStorage (XSS risk)
  • ❌ Include tokens in URLs (logs/history exposure)
  • ❌ Send tokens over unencrypted connections
  • ❌ Share tokens between users

3. Token Transmission

Always use HTTPS:

Terminal window
# ❌ INSECURE
curl -H "Authorization: Bearer $TOKEN" http://api.example.com/data
# SECURE
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data

4. Rate Limiting

// Enable rate limiting in production
let config = JwtConfig {
rate_limit_enabled: true,
rate_limit_per_second: 50, // Adjust based on load
..Default::default()
};

5. Monitoring

// Track validation attempts
let validation_result = validator.validate(&token).await;
match validation_result {
Ok(claims) => {
// Log successful authentication
tracing::info!(
user_id = %claims.sub,
jti = ?claims.jti,
"Token validated successfully"
);
}
Err(e) => {
// Log failed authentication
tracing::warn!(
error = %e,
token_preview = &token[..20],
"Token validation failed"
);
}
}

API Integration

REST API (Axum)

use axum::{
Router,
routing::get,
middleware,
};
use heliosdb_rest::auth::{require_auth, HasAuthConfig};
let app = Router::new()
.route("/api/protected", get(protected_handler))
.layer(middleware::from_fn_with_state(
app_state.clone(),
require_auth,
))
.with_state(app_state);

GraphQL

use heliosdb_graphql::auth::{TokenManager, Claims};
// In GraphQL context
pub struct Context {
token_manager: TokenManager,
claims: Option<Claims>,
}
// In resolver
fn resolve_user(&self, ctx: &Context) -> Result<User> {
let claims = ctx.claims.as_ref()
.ok_or("Not authenticated")?;
if !claims.has_role("admin") {
return Err("Insufficient permissions".into());
}
// ... rest of resolver
}

HTTP Gateway

use heliosdb_protocols::http_gateway::HttpGateway;
let gateway = HttpGateway::new()?;
// JWT validator is automatically used for authentication
// All Snowflake/Databricks/Pinecone endpoints require valid JWT

Protocol Handlers

use heliosdb_protocols::auth::{JwtValidator, JwtConfig};
// Create validator for protocol handler
let jwt_validator = JwtValidator::new(JwtConfig::default())?;
// Validate tokens in custom handlers
async fn handle_request(token: &str) -> Result<()> {
let claims = jwt_validator.validate(token).await?;
// Check permissions
if !claims.permissions.contains(&"admin".to_string()) {
return Err("Forbidden".into());
}
Ok(())
}

Troubleshooting

Common Errors

1. “Token has expired”

Cause: Token exp claim is in the past

Solution:

// Increase token lifetime (development only)
config.token_lifetime = Duration::hours(24);
// Production: Implement token refresh
let new_token = validator.refresh_token(&refresh_token).await?;

2. “Invalid token: Invalid signature”

Cause: Token signature doesn’t match, or wrong key used

Solutions:

  • Verify same key is used for signing and validation
  • Check if key rotation occurred
  • Ensure token wasn’t tampered with

3. “Rate limit exceeded”

Cause: Too many validation requests

Solutions:

// Increase rate limit (if legitimate load)
config.rate_limit_per_second = 200;
// Or disable for testing
config.rate_limit_enabled = false;

4. “Token has been revoked”

Cause: Token was explicitly revoked

Solution:

// Get new token
let new_token = validator.issue_token(user_id, permissions, metadata)?;

5. “Missing authorization header”

Cause: No Authorization header in request

Solution:

Terminal window
# Always include Bearer token
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/endpoint

Debugging Tools

Inspect Token

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
fn decode_jwt_payload(token: &str) -> anyhow::Result<serde_json::Value> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
anyhow::bail!("Invalid JWT format");
}
let payload = URL_SAFE_NO_PAD.decode(parts[1])?;
Ok(serde_json::from_slice(&payload)?)
}

Check Token Expiration

let payload = decode_jwt_payload(&token)?;
let exp = payload["exp"].as_i64().unwrap();
let now = chrono::Utc::now().timestamp();
println!("Token expires at: {}", exp);
println!("Current time: {}", now);
println!("Time remaining: {} seconds", exp - now);

Validate Without Rate Limiting

// Temporarily disable rate limiting for debugging
let mut debug_config = config.clone();
debug_config.rate_limit_enabled = false;
let debug_validator = JwtValidator::new(debug_config)?;
let claims = debug_validator.validate(&token).await?;

Advanced Topics

1. Custom Claims

use heliosdb_security::jwt::JwtClaims;
use chrono::Utc;
use uuid::Uuid;
let custom_claims = JwtClaims {
sub: "user123".to_string(),
exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
iat: Utc::now().timestamp(),
nbf: Some(Utc::now().timestamp()),
aud: Some("heliosdb-api".to_string()),
iss: Some("heliosdb".to_string()),
jti: Some(Uuid::new_v4().to_string()),
permissions: vec!["admin".to_string()],
session_id: Some("session_123".to_string()),
metadata: {
let mut meta = HashMap::new();
meta.insert("tenant_id".to_string(), "tenant_456".to_string());
meta.insert("ip_address".to_string(), "192.168.1.1".to_string());
meta
},
};
let token = validator.create_token(&custom_claims)?;

2. JWKS (JSON Web Key Set) Endpoint

use axum::{routing::get, Json};
async fn jwks_endpoint(
State(validator): State<Arc<JwtValidator>>,
) -> Json<serde_json::Value> {
let jwks = validator.get_jwks().unwrap();
Json(jwks)
}
// Mount endpoint
let app = Router::new()
.route("/.well-known/jwks.json", get(jwks_endpoint));

Response:

{
"keys": [
{
"kid": "abc123",
"alg": "RS256",
"use": "sig",
"kty": "RSA"
}
]
}

3. Redis-Based Revocation

use heliosdb_security::jwt::RedisRevocationList;
#[cfg(feature = "redis-revocation")]
{
let revocation_list = Arc::new(
RedisRevocationList::new(
"redis://localhost:6379",
"heliosdb".to_string(),
).await?
);
let validator = JwtValidator::with_revocation_list(
config,
revocation_list,
)?;
}

4. Multi-Tenancy

// Include tenant ID in metadata
let mut metadata = HashMap::new();
metadata.insert("tenant_id".to_string(), tenant_id.clone());
let token = validator.issue_token(user_id, permissions, metadata)?;
// Validate tenant isolation in handler
async fn handle_request(claims: &JwtClaims, resource: &Resource) -> Result<()> {
let tenant_id = claims.metadata.get("tenant_id")
.ok_or("Missing tenant ID")?;
if resource.tenant_id != tenant_id {
return Err("Tenant isolation violation".into());
}
Ok(())
}

5. Performance Optimization

// Use connection pooling for validation
use dashmap::DashMap;
use std::sync::Arc;
struct TokenCache {
cache: Arc<DashMap<String, (JwtClaims, i64)>>,
}
impl TokenCache {
fn get(&self, token: &str) -> Option<JwtClaims> {
self.cache.get(token).and_then(|entry| {
let (claims, exp) = entry.value();
if *exp > chrono::Utc::now().timestamp() {
Some(claims.clone())
} else {
None
}
})
}
fn set(&self, token: String, claims: JwtClaims) {
self.cache.insert(token, (claims.clone(), claims.exp));
}
}

Reference

Configuration Options

OptionTypeDefaultDescription
issuerOption<String>Some("heliosdb")Required token issuer
audienceOption<String>Some("heliosdb-api")Required token audience
algorithmsVec<Algorithm>[RS256]Allowed signature algorithms
leewayu6460Clock skew tolerance (seconds)
token_lifetimeDuration1 hourAccess token lifetime
refresh_lifetimeDuration7 daysRefresh token lifetime
rate_limit_enabledbooltrueEnable rate limiting
rate_limit_per_secondu32100Max validations per second

JWT Claims

ClaimTypeRequiredDescription
subStringYesSubject (user ID)
expi64YesExpiration time (Unix timestamp)
iati64YesIssued at (Unix timestamp)
nbfOption<i64>NoNot before (Unix timestamp)
audOption<String>NoAudience
issOption<String>NoIssuer
jtiOption<String>NoJWT ID (required for revocation)
permissionsVec<String>NoCustom permissions
session_idOption<String>NoSession tracking ID
metadataHashMap<String, String>NoCustom key-value metadata

Algorithms

AlgorithmSecurityKey TypeRecommended
RS256HighRSA 2048-bitYes (default)
RS384HighRSA 3072-bitYes
RS512HighRSA 4096-bitYes
ES256HighECDSA P-256Yes
ES384HighECDSA P-384Yes
HS256MediumSymmetric⚠ Dev only
HS384MediumSymmetric⚠ Dev only
HS512MediumSymmetric⚠ Dev only

Recommendation: Use RS256 for production deployments.


Support

For issues or questions:

Version: 7.0.0 Last Updated: 2025-11-10