Identity Tokens as API Keys
Hessra identity tokens can now be verified as bearer tokens, making them perfect drop-in replacements for traditional API keys. Get immediate security improvements with automatic expiration and cryptographic signatures, while maintaining a clear upgrade path to full authorization.
> Why Replace API Keys?
Traditional API keys have fundamental problems:
Traditional API Keys | Hessra Identity Tokens |
---|---|
Static strings in databases | Self-contained cryptographic tokens |
Manual expiration tracking | Automatic expiration built-in |
Database lookups for validation | Offline verification with public key |
No identity information | Embedded identity for auditing |
Complex rotation processes | Simple time-based rotation |
No delegation capability | Hierarchical delegation support |
> Prerequisites
Before implementing identity tokens as API keys, you'll need:
- Hessra service access: Join the waitlist or email hello@hessra.net
- Service keypair: For signing identity tokens
- Public key distribution: Method to share your public key with verifying services
> Installation
Rust SDK
[dependencies]
hessra-sdk = "0.10.0"
hessra-token-identity = "0.2.0" # For bearer verification
Python SDK
pip install hessra-py
CLI Tool
cargo install hessra
> Basic Implementation
Step 1: Issue Identity Tokens to Customers
Instead of generating random API keys, issue identity tokens:
use hessra_sdk::Hessra;
use hessra_token_core::TokenTimeConfig;
// Your service's Hessra client
let client = Hessra::builder()
.base_url("auth.yourcompany.com")
.mtls_cert(&cert)
.mtls_key(&key)
.server_ca(&ca)
.build()?;
// Issue an API key (identity token) for a customer
let identity_response = client
.request_identity_token(Some("customer:acme-corp"))
.await?;
// This IS the API key - send to customer
let api_key = identity_response.token;
Using the CLI:
# Issue API key for customer
hessra identity authenticate \
--server auth.yourcompany.com \
--cert service.crt \
--key service.key \
--ca ca.crt \
--identity "customer:acme-corp" \
--ttl 2592000 # 30 days
Step 2: Verify as Bearer Token
Your services verify the token without checking identity:
use hessra_token_identity::{verify_bearer_token, PublicKey};
// Your service's public key (can be distributed freely)
let public_key = PublicKey::from_bytes(&key_bytes)?;
// Verify incoming API key
fn verify_api_key(auth_header: &str) -> Result<()> {
let token = extract_bearer_token(auth_header)?;
// Bearer verification: only checks signature and expiration
verify_bearer_token(token, public_key)?;
Ok(())
}
Python verification:
from hessra_py import verify_bearer_token, PublicKey
def verify_api_key(auth_header: str):
token = auth_header.replace("Bearer ", "")
public_key = PublicKey.from_bytes(key_bytes)
# Throws exception if invalid or expired
verify_bearer_token(token, public_key)
return True
Step 3: Extract Identity for Logging
Even in bearer mode, you can inspect tokens for audit trails:
use hessra_token_identity::{inspect_identity_token, verify_bearer_token};
// First verify as bearer token
verify_bearer_token(&token, public_key)?;
// Then inspect for logging/metrics
let info = inspect_identity_token(&token, public_key)?;
log::info!(
"API request from: {} (expires: {:?})",
info.identity,
info.expiry
);
> Advanced Patterns
JIT (Just-In-Time) Attenuation
Protect long-lived tokens by creating ultra-short-lived versions for network transmission:
use hessra_token_identity::create_short_lived_identity_token;
pub struct SecureAPIClient {
identity_token: String, // Long-lived, stays on client
public_key: PublicKey,
}
impl SecureAPIClient {
pub async fn make_request(&self, endpoint: &str) -> Result<Response> {
// Create 5-second token just for this request
let wire_token = create_short_lived_identity_token(
self.identity_token.clone(),
self.public_key
)?;
// Only short-lived token goes over network
let response = reqwest::Client::new()
.get(endpoint)
.header("Authorization", format!("Bearer {}", wire_token))
.send()
.await?;
Ok(response)
}
}
Python SDK integration:
from hessra_py import create_short_lived_identity_token
import requests
class SecureAPIClient:
def __init__(self, api_key, public_key):
self.api_key = api_key # Never sent over network
self.public_key = public_key
def request(self, endpoint):
# Create 5-second token for this request
wire_token = create_short_lived_identity_token(
self.api_key,
self.public_key
)
return requests.get(
endpoint,
headers={"Authorization": f"Bearer {wire_token}"}
)
Tiered API Keys
Different customer tiers with different expiration times:
use hessra_token_core::TokenTimeConfig;
use chrono::Duration;
fn issue_api_key(customer_id: &str, tier: &str) -> Result<String> {
let duration = match tier {
"free" => Duration::days(7),
"pro" => Duration::days(30),
"enterprise" => Duration::days(365),
_ => Duration::days(30),
};
let config = TokenTimeConfig {
start_time: None,
duration: duration.num_seconds() as u64,
};
let token = create_identity_token(
format!("customer:{}:tier:{}", customer_id, tier),
keypair,
config
)?;
Ok(token)
}
Customer Self-Delegation
Enable customers to create sub-keys for their teams:
# Customer delegates to team member
hessra identity delegate \
--from-token $CUSTOMER_API_KEY \
--identity "customer:acme-corp:team:dev" \
--ttl 604800 # 1 week
# Team member delegates to CI/CD
hessra identity delegate \
--from-token $TEAM_API_KEY \
--identity "customer:acme-corp:team:dev:ci" \
--ttl 3600 # 1 hour for CI jobs
> Handling Token Rotation
Automatic Renewal Pattern
Help your customers handle token expiration gracefully:
import time
from datetime import datetime, timedelta
class APIClient:
def __init__(self, get_new_token_func):
self.get_new_token = get_new_token_func
self.token = None
self.token_expiry = None
def ensure_valid_token(self):
# Renew if token expires in less than 7 days
if not self.token or self.token_expiry < datetime.now() + timedelta(days=7):
token_response = self.get_new_token()
self.token = token_response['token']
self.token_expiry = datetime.fromtimestamp(token_response['expires_at'])
def make_request(self, endpoint):
self.ensure_valid_token()
return requests.get(endpoint, headers={"Authorization": f"Bearer {self.token}"})
Customer Communication
Notify customers before expiration:
# Weekly job to check expiring tokens
for customer in customers_with_tokens:
days_until_expiry = (customer.token_expiry - datetime.now()).days
if days_until_expiry == 30:
send_email(customer, "API key expires in 30 days - renew now")
elif days_until_expiry == 7:
send_email(customer, "URGENT: API key expires in 7 days")
elif days_until_expiry == 1:
send_email(customer, "CRITICAL: API key expires tomorrow")
> Migration Guide
Parallel Operation
Run both systems while migrating:
fn verify_api_credential(auth_header: &str) -> Result<Identity> {
if auth_header.starts_with("Bearer hessra_") {
// New identity token
let token = extract_token(auth_header)?;
verify_bearer_token(token, public_key)?;
let info = inspect_identity_token(token, public_key)?;
Ok(Identity::from(info.identity))
} else if auth_header.starts_with("Bearer sk_") {
// Legacy API key
let key = extract_token(auth_header)?;
let identity = lookup_legacy_api_key(&key)?;
Ok(identity)
} else {
Err(Error::InvalidCredential)
}
}
Migration Steps
- Deploy verification support: Update services to accept both token types
- Issue new tokens: Start issuing identity tokens to new customers
- Notify existing customers: Provide migration timeline and new tokens
- Monitor adoption: Track usage of old vs new tokens
- Deprecate old system: Remove legacy API key support
Customer Communication Template
## Upgrading to New API Keys
We're upgrading our API key system for better security:
**What's changing:**
- New keys expire automatically (30/60/90 days based on your plan)
- Keys can be delegated to team members
- Better security with cryptographic signatures
**Action required:**
1. Generate your new API key at dashboard.yourcompany.com
2. Update your integration to use the new key
3. Old keys will stop working on [DATE]
**No code changes needed** - new keys work exactly like old ones.
> Configuration Reference
Public Key Distribution
Your public key must be available to services that verify tokens:
// Option 1: Environment variable
let public_key = PublicKey::from_bytes(
&base64::decode(env::var("HESSRA_PUBLIC_KEY")?)?
)?;
// Option 2: Configuration file
let config = HessraConfig::from_file("hessra.toml")?;
let public_key = config.public_key()?;
// Option 3: Well-known endpoint
let public_key = fetch_public_key("https://auth.yourco.com/.well-known/hessra-public-key").await?;
// Option 4: From Hessra service
let public_key = fetch_public_key("https://yourco.hessra.net/public_key").await?;
Token Configuration
Configure token parameters in your GitOps repository:
# clients.toml
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:service:api" }
# Enable identity tokens for this client
identity_token_enabled = true
# Token duration in seconds
identity_token_duration = 2592000 # 30 days
# Optional: Set shorter duration for specific patterns
[[clients]]
identifier = { type = "san_dns", value = "*.trial.yourco.com" }
identity_token_enabled = true
identity_token_duration = 604800 # 7 days for trial accounts
> Security Considerations
Understanding the Cryptography
Identity tokens are Biscuit tokens - a modern, capability-based token format that uses:
- Ed25519 or P-256 signatures: Cryptographically secure, cannot be forged
- Datalog-based authorization logic: Constraints that must ALL be satisfied
- Attenuation blocks: Each delegation adds restrictions, never removes them
- Third-party blocks: Cryptographically isolated extensions
This isn't a proprietary format - Biscuit is an open standard with implementations in multiple languages.
Token Expiration Strategy
Use Case | Recommended Duration | Rationale |
---|---|---|
Production API Keys | 30-90 days | Balance security with user convenience |
Development Keys | 7-14 days | Higher risk environment |
CI/CD Keys | 1-24 hours | Automated renewal possible |
JIT Tokens | 5 seconds | Network transmission only |
Revocation Patterns and Trade-offs
The stateless model means no granular revocation without additional infrastructure. Your options:
-
Short expiration (Primary approach)
- Let tokens expire naturally
- Works well with 30-90 day tokens
- Zero infrastructure required
-
Public key rotation (Emergency only)
- Invalidates ALL tokens immediately
- Requires redistributing new public key
- Use only for critical security incidents
-
Local blocklists (Hybrid approach)
Biscuit tokens have built-in revocation support with unique identifiers. You can block:
- Specific tokens by their revocation ID (coming to Hessra SDK)
- Identities extracted from tokens (available now)
// Current approach: block by identity let blocked_identities = HashSet::from(["customer:compromised"]); verify_bearer_token(&token, public_key)?; let info = inspect_identity_token(&token, public_key)?; if blocked_identities.contains(&info.identity) { return Err("Token revoked"); } // Future: Biscuit revocation IDs // Each token has a unique revocation_id that can be blocked // without affecting other tokens from the same identity
-
Hessra authorization service (Future)
- Adds stateful revocation capabilities
- Maintains benefits of offline verification for normal operations
- Revocation checks only when needed
Private Key Management
Using Hessra Service (Recommended):
- We handle key generation, storage, and rotation
- Keys stored in HSM-backed infrastructure
- Automatic key rotation with zero downtime
Self-Hosting with SDK:
- Store private keys in HSM or KMS (AWS KMS, Google Cloud KMS, Azure Key Vault)
- Never store private keys in code or environment variables
- Implement key rotation strategy (quarterly recommended)
- Consider using separate keys for different environments
Example with AWS KMS:
use aws_sdk_kms::Client as KmsClient;
async fn sign_with_kms(message: &[u8]) -> Result<Vec<u8>> {
let kms = KmsClient::new(&aws_config::load_from_env().await);
let response = kms
.sign()
.key_id("arn:aws:kms:region:account:key/id")
.message(Blob::new(message))
.signing_algorithm(SigningAlgorithmSpec::EcdsaSha256)
.send()
.await?;
Ok(response.signature.unwrap().into_inner())
}
Performance Considerations
Approximate token verification performance (your results will vary based on hardware and implementation):
Operation | Rough Order of Magnitude | Notes |
---|---|---|
Ed25519 signature verification | Sub-millisecond | Generally faster than P-256 |
P-256 signature verification | Sub-millisecond | NIST standard, slightly slower |
Database query (comparison) | 1-10ms | Includes network round-trip |
In-memory string comparison | Microseconds | But requires state management |
Important: These are rough estimates. Actual performance depends on:
- CPU architecture and speed
- Implementation quality (language, libraries)
- Network latency (for database queries)
- Load and caching strategies
We recommend benchmarking in your specific environment. For most applications, the cryptographic overhead is negligible compared to network I/O.
For high-throughput services:
- Cache verification results for repeated tokens (with TTL)
- Use Ed25519 over P-256 for better performance
- Consider connection pooling for Hessra service calls
- Benchmark with your actual workload
Attenuation Security Model
Delegation can only narrow scope, never expand:
// Original token for "customer:acme"
let original = create_identity_token("customer:acme", keypair, config)?;
// ✅ Valid: narrowing to sub-identity
let valid = attenuate_identity_token(
&original,
"customer:acme:team:dev", // More specific
public_key
)?;
// ❌ Invalid: would be rejected by the token logic
let invalid = attenuate_identity_token(
&original,
"customer:bigcorp", // Different customer - not allowed
public_key
)?;
// ❌ Invalid: would be rejected by the token logic
let invalid = attenuate_identity_token(
&original,
"admin:superuser", // Privilege escalation - not allowed
public_key
)?;
Replay Attack Mitigation
While tokens are time-bound, they can be replayed within their validity window:
For idempotent operations (GET, PUT):
- Token replay is generally safe
- Use standard HTTP caching headers
For non-idempotent operations (POST, financial transactions):
- Add request-specific nonces
- Use request signing in addition to bearer tokens
- Consider shorter token expiration (seconds, not minutes)
Example with nonce:
struct AuthorizedRequest {
token: String,
nonce: String, // UUID or timestamp+random
signature: String, // Sign(token + nonce + body)
}
Best Practices
- Always use HTTPS: Even with signed tokens
- Implement rate limiting: Tokens don't prevent abuse
- Log token usage: Track identity for audit trails
- Monitor expiration: Alert customers before tokens expire
- Use JIT attenuation: When possible, keep long-lived tokens off the network
> Upgrade Path to Full Authorization
Identity tokens as API keys are just the beginning:
Today: Bearer Tokens
- Simple API key replacement
- Automatic expiration
- Identity tracking
Tomorrow: JIT Attenuation
- SDK integration
- 5-second network tokens
- Original tokens stay secure
Next Week: Scoped Authorization
- Exchange identity for authorization tokens
- Fine-grained permissions
- Single-action tokens
Next Month: Full Delegation
- Customer self-service
- Team management
- Hierarchical access control
> Quick Reference
CLI Commands
# Issue identity token (API key)
hessra identity authenticate --identity "customer:id"
# Verify token
hessra identity verify --token $TOKEN
# Delegate token
hessra identity delegate --from-token $TOKEN --identity "sub:identity"
# Inspect token
hessra identity inspect --token $TOKEN
SDK Functions
// Rust
use hessra_token_identity::{
verify_bearer_token, // Bearer verification
verify_identity_token, // Full identity verification
inspect_identity_token, // Extract identity info
create_short_lived_identity_token, // JIT attenuation
};
// Python
from hessra_py import (
verify_bearer_token, # Bearer verification
verify_identity_token, # Full identity verification
inspect_identity_token, # Extract identity info
create_short_lived_identity_token, # JIT attenuation
)
> Getting Help
- Blog post: How to Implement API Keys Using Hessra Identity Tokens
- Identity guide: Getting Started with Identity
- Code examples: GitHub
- Support: hello@hessra.net
Start with bearer tokens today. Grow into full authorization tomorrow. No migration required.