Getting Started with Hessra Authorization
Hessra Authorization provides cryptographically verifiable, short-lived tokens for fine-grained access control. Unlike traditional authorization systems, Hessra tokens are designed to be verified offline, carry minimal authority, and support advanced patterns like service chains and multi-party authorization.
> Core Concepts
Authorization Tokens
Authorization tokens are the heart of Hessra's security model:
- Short-lived: 5 minutes or less (configurable down to seconds)
- Single-action: Scoped to one resource and operation
- Meant to be used once: Request → Use → Discard pattern
- Offline-verifiable: No database or network calls needed
Authentication Methods
Request authorization tokens using:
- mTLS certificates: Traditional X.509 client certificates
- Identity tokens: Hessra identity tokens (see identity documentation)
Policy Configuration
Authorization decisions are made based on GitOps-style configuration:
- TOML files in a git repository
- Versioned, auditable policy changes
- Automatic sync with authorization service
> Basic Authorization Flow
1. Configure Your Policy
In your policy repository's clients.toml
:
# Define which certificates can connect
[cacerts]
cert_chain = '''-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIUKb5f7cc...
-----END CERTIFICATE-----'''
# Define client permissions
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:service:api-gateway" }
resources = [
{ name = "users", operations = ["read", "write", "delete"] },
{ name = "orders", operations = ["read", "create"] }
]
[[clients]]
identifier = { type = "san_dns", value = "worker-*.internal.com" }
resources = [
{ name = "queue", operations = ["read", "process"] }
]
2. Request Authorization Tokens
Using the Rust SDK:
use hessra_sdk::Hessra;
// Initialize with mTLS
let mut client = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(MTLS_CERT)
.mtls_key(MTLS_KEY)
.server_ca(SERVER_CA)
.build()?;
// Setup fetches the public key for offline verification
client.setup().await?;
// Request a token for specific action
let token = client
.request_token("users", "read")
.await?;
println!("Token received: {}...", &token[..50]);
Using the CLI:
hessra auth request \
--server auth.your-domain.com \
--cert client.crt \
--key client.key \
--ca ca.crt \
--resource users \
--operation read
3. Pass Tokens with Requests
Include the token when making requests to your services:
// HTTP request with authorization token
let response = reqwest::Client::new()
.get("https://api.your-domain.com/users/123")
.header("Authorization", format!("Bearer {}", token))
.header("x-subject", "uri:urn:service:api-gateway")
.header("x-resource", "users")
.header("x-operation", "read")
.send()
.await?;
4. Verify Tokens in Services
Services verify tokens locally without network calls:
// In your service endpoint
fn verify_request(token: &str, subject: &str, resource: &str, operation: &str) -> Result<()> {
// Verification is synchronous and offline
client.verify_token_local(
token,
subject,
resource,
operation
)?;
// Token is valid - proceed with request
Ok(())
}
> Advanced Feature: Service Chains
Service chains ensure requests pass through the correct sequence of services, preventing lateral movement and providing cryptographic proof of traversal.
Understanding Service Chains
In distributed systems, requests often traverse multiple services:
Client → API Gateway → Auth Middleware → Business Logic → Database
Service chains let each service:
- Verify the token is valid
- Confirm all previous services in the chain authorized it
- Add its own attestation for downstream services
Configuring Service Chains
In your resources.toml
:
[resources.payment_processor]
type = "service_chain"
nodes = [
{
component = "api_gateway",
public_key = "ed25519/e57618058b1d2e0381a9813c1405830d5ed7d603717384ef555d9cc0cfa65d83"
},
{
component = "fraud_detection",
public_key = "ed25519/78ef4255c4c9ab5c7186d6db4760758b06616c042a2538323ae3b058094034b6"
},
{
component = "payment_processor",
public_key = "ed25519/1aebc5a6eefc569051926a6aaf55568e53edb475f2f2eb904522609391d88113"
}
]
Implementing Service Chains
Each service needs its own keypair and adds attestations:
// API Gateway (first service)
let api_gateway = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(GATEWAY_CERT)
.mtls_key(GATEWAY_KEY)
.server_ca(CA_CERT)
.personal_keypair(GATEWAY_KEYPAIR) // Ed25519 or P-256 keypair
.build()?;
// Request initial token
let token = api_gateway
.request_token("payment_processor", "charge")
.await?;
// Add attestation before forwarding
let attested_token = api_gateway
.attest_service_chain_token(&token, "payment_processor")?;
// Forward to fraud detection service
forward_to_fraud_detection(attested_token);
// Fraud Detection Service (middle service)
let fraud_service = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(FRAUD_CERT)
.mtls_key(FRAUD_KEY)
.server_ca(CA_CERT)
.personal_keypair(FRAUD_KEYPAIR)
.build()?;
// Verify the token came through API gateway
let partial_chain = ServiceChain::new()
.with_node(ServiceNode {
component: "api_gateway".to_string(),
public_key: "ed25519/e57618058b1d2e0381a9813c1405830d5ed7d603717384ef555d9cc0cfa65d83".to_string()
});
fraud_service.verify_service_chain_token_local(
&token,
"uri:urn:user:123",
"payment_processor",
"charge",
&partial_chain,
Some("fraud_detection") // We are fraud_detection
)?;
// Add our attestation
let double_attested = fraud_service
.attest_service_chain_token(&token, "payment_processor")?;
// Forward to payment processor
forward_to_payment_processor(double_attested);
// Payment Processor (final service)
let processor = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(PROCESSOR_CERT)
.mtls_key(PROCESSOR_KEY)
.server_ca(CA_CERT)
.personal_keypair(PROCESSOR_KEYPAIR)
.build()?;
// Verify complete chain
let full_chain = ServiceChain::new()
.with_node(ServiceNode {
component: "api_gateway".to_string(),
public_key: "ed25519/e57618058b1d2e0381a9813c1405830d5ed7d603717384ef555d9cc0cfa65d83".to_string()
})
.with_node(ServiceNode {
component: "fraud_detection".to_string(),
public_key: "ed25519/78ef4255c4c9ab5c7186d6db4760758b06616c042a2538323ae3b058094034b6".to_string()
});
processor.verify_service_chain_token_local(
&token,
"uri:urn:user:123",
"payment_processor",
"charge",
&full_chain,
None // We're the final service
)?;
// Token verified through complete chain - process payment
process_payment();
Service Chain Benefits
- Proof of Traversal: Cryptographic evidence of the exact path taken
- Lateral Movement Prevention: Compromised services can't bypass the chain
- Distributed Trust: No single service can forge the complete chain
- Audit Trail: Complete record of which services handled each request
> Advanced Feature: Multi-Party Authorization
Multi-party authorization requires multiple authorization services to approve a token, enabling cross-organization security policies.
Use Cases
Multi-party authorization is ideal for:
- Cross-Organization Access: Two companies jointly authorizing data access
- Business Unit Separation: Different departments must both approve
- Vendor Software: Customer controls access to vendor-deployed software
- Compliance Requirements: Multiple approval authorities required by regulation
How It Works
- Request token from primary authorization service
- Token includes
pending_signoffs
for other required services - Each additional service signs the token based on their policy
- Final token is valid only with all signatures
Configuration
In your primary service's resources.toml
:
[resources.sensitive_data]
type = "multi_party"
required_signers = [
{
name = "legal_dept",
url = "https://legal.auth.company.com",
public_key = "ed25519/a4b5c6d7e8f9..."
},
{
name = "partner_company",
url = "https://auth.partner.com",
public_key = "ed25519/1a2b3c4d5e6f..."
}
]
Implementation
Using the SDK's automatic collection:
// Request initial token
let token_response = client
.request_token_full("sensitive_data", "export")
.await?;
if let Some(pending) = &token_response.pending_signoffs {
println!("Token requires {} additional signatures", pending.len());
// Automatically collect all required signatures
let signed_token = client
.collect_multi_party_signoffs(
token_response,
"sensitive_data",
"export"
)
.await?;
println!("All signatures collected!");
use_token(signed_token);
}
Manual signature collection:
// Get initial token
let mut current_token = token_response.token.unwrap();
// For each required signer
for signoff in token_response.pending_signoffs.unwrap() {
// Create client for that authorization service
let signer_client = Hessra::builder()
.base_url(&signoff.url)
.mtls_cert(&our_cert) // Use your credentials
.mtls_key(&our_key)
.server_ca(&their_ca)
.build()?;
// Request their signature
current_token = signer_client
.sign_token(¤t_token, "sensitive_data", "export")
.await?;
println!("Received signature from {}", signoff.name);
}
// Token now has all required signatures
Multi-Party Policy Coordination
Each authorization service evaluates based on its own policy:
Company A's policy:
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:partner:service" }
resources = [
{ name = "sensitive_data", operations = ["export"] }
]
# Additional constraints: business hours only
Company B's policy:
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:partner:service" }
resources = [
{ name = "sensitive_data", operations = ["export"] }
]
# Additional constraints: requires manager approval flag
Both policies must approve for the token to be valid.
> Configuration with GitOps
Hessra uses a GitOps approach for managing authorization policies. Your configuration lives in TOML files in a git repository, providing versioned, auditable control over authorization decisions.
Repository Structure
Your GitOps repository contains two main configuration files:
your-policy-repo/
├── clients.toml # Client authentication and permissions
└── resources.toml # Resource definitions and mechanisms
Configuring Client Authorization
The clients.toml
file defines who can authenticate and what resources they can access:
# Version for compatibility tracking
version = "0.2.0"
# CA certificates that authenticate clients
[cacerts]
cert_chain = '''-----BEGIN CERTIFICATE-----
...your CA certificates...
-----END CERTIFICATE-----'''
# Service with broad permissions
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:services:api-gateway" }
resources = [
{ name = "users", operations = ["read", "write", "delete"] },
{ name = "orders", operations = ["read", "write"] },
{ name = "payments", operations = ["read", "write"] }
]
# Microservice with specific permissions
[[clients]]
identifier = { type = "san_dns", value = "payment-processor.internal" }
resources = [
{ name = "payments", operations = ["read", "write", "process"] },
{ name = "audit_log", operations = ["write"] }
]
# Wildcard for worker fleet
[[clients]]
identifier = { type = "san_dns", value = "worker-*.internal.com" }
resources = [
{ name = "queue", operations = ["read", "process"] },
{ name = "metrics", operations = ["write"] }
]
# Client with identity token support
[[clients]]
identifier = { type = "serial", value = "1234567890" }
resources = [
{ name = "api", operations = ["read"] }
]
identity_token_enabled = true
identity_token_duration = 3600
Identifier Types
Hessra supports multiple ways to identify clients from certificates:
san_uri
: Subject Alternative Name URI (e.g.,uri:urn:services:api
)san_dns
: DNS name with wildcard support (e.g.,*.internal.com
)san_email
: Email addresssan_ip
: IP addressserial
: Certificate serial number
Configuring Resources and Mechanisms
The resources.toml
file defines how resources are protected:
version = "0.2.0"
# Simple resource (default authorization)
[resources.users]
# No special configuration needed for basic resources
# Service chain resource
[resources.payment_processing]
mechanisms = ["service_chain"]
[resources.payment_processing.service_chain]
nodes = [
{
component = "api_gateway",
public_key = "ed25519/e57618058b1d2e0381a9813c1405830d5ed7d603717384ef555d9cc0cfa65d83"
},
{
component = "fraud_detection",
public_key = "ed25519/78ef4255c4c9ab5c7186d6db4760758b06616c042a2538323ae3b058094034b6"
},
{
component = "payment_processor",
public_key = "ed25519/1aebc5a6eefc569051926a6aaf55568e53edb475f2f2eb904522609391d88113"
}
]
# Multi-party authorization resource
[resources.cross_org_data]
mechanisms = ["multi_party"]
[resources.cross_org_data.multi_party]
required_signers = [
{
name = "partner_org",
url = "https://auth.partner.com",
public_key = "ed25519/a4b5c6d7e8f9a0b1c2d3e4f5667788990abbccddeeff0011223344556677889900"
},
{
name = "compliance_dept",
url = "https://compliance.internal.com",
public_key = "ed25519/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
]
# Mixed mechanism resource (future)
[resources.sensitive_operation]
mechanisms = ["service_chain", "multi_party"]
# Configuration for both mechanisms...
GitOps Workflow
The authorization service automatically syncs with your git repository:
- Push Changes: Update configuration files in your repository
- Automatic Pull: Authorization service detects and pulls changes
- Validation: Configurations are validated before applying
- Live Update: New policies take effect without service restart
- Rollback: Revert git commits to rollback policy changes
Policy Validation
The authorization service validates configurations before applying:
# Local validation (if using Hessra CLI tools)
hessra policy validate --path ./policy-repo
# Validation checks:
# - TOML syntax correctness
# - Certificate chain validity
# - Public key format validation
# - Resource reference consistency
# - No conflicting permissions
Best Practices for Configuration
Resource Naming
Use consistent, hierarchical naming:
# Good
resources = [
{ name = "api.users", operations = ["read", "write"] },
{ name = "api.orders", operations = ["read"] },
{ name = "db.users", operations = ["read"] }
]
# Avoid
resources = [
{ name = "users", operations = ["read"] },
{ name = "UserData", operations = ["write"] },
{ name = "user-info", operations = ["read"] }
]
Operation Standards
Define consistent operation names across resources:
- CRUD:
create
,read
,update
,delete
- Special:
execute
,process
,approve
,audit
- Batch:
bulk_read
,bulk_write
,bulk_delete
Permission Minimization
Grant minimum necessary permissions:
# Too broad
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:service:worker" }
resources = [
{ name = "*", operations = ["*"] } # Avoid wildcards
]
# Better
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:service:worker" }
resources = [
{ name = "queue", operations = ["read", "process"] },
{ name = "metrics", operations = ["write"] }
]
Service Chain Configuration
Always list nodes in order of expected traversal:
# Correct order: edge → middle → backend
nodes = [
{ component = "cdn", public_key = "..." },
{ component = "api_gateway", public_key = "..." },
{ component = "backend", public_key = "..." }
]
Environment-Specific Configuration
Use branches or separate repositories for different environments:
# Repository structure
main # Production policies
├── staging # Staging environment
└── development # Development environment
# Or separate repositories
policy-prod/ # Production only
policy-staging/ # Staging only
policy-dev/ # Development only
Audit and Compliance
GitOps provides automatic audit trails:
- Who: Git commit author
- What: Diff of configuration changes
- When: Commit timestamp
- Why: Commit message with ticket reference
- Approval: PR reviews and merge approvals
Example commit message:
feat(auth): Grant payment service access to audit logs
- Added write permission for audit_log resource
- Required for compliance with PCI-DSS logging
Ticket: SEC-1234
Reviewed-by: security-team
> Integration Patterns
Database Integration with PostgreSQL Extension
Use the Hessra PGRX extension for Row Level Security:
-- Install the extension
CREATE EXTENSION IF NOT EXISTS hessra_authz;
-- Create RLS policy using Hessra verification
CREATE POLICY user_data_access ON user_data
FOR SELECT
USING (
hessra_authz.verify_token(
current_setting('hessra.token'),
current_setting('hessra.subject'),
'user_data',
'read'
)
);
-- In your application
SET LOCAL hessra.token = 'your_token_here';
SET LOCAL hessra.subject = 'uri:urn:user:123';
SELECT * FROM user_data; -- RLS automatically enforced
Microservice Mesh Pattern
Implement zero-trust between all services:
// Middleware for automatic token verification
async fn auth_middleware(req: Request, next: Next) -> Response {
let token = match req.headers().get("authorization") {
Some(h) => h.to_str().unwrap().replace("Bearer ", ""),
None => return Response::unauthorized()
};
let subject = req.headers().get("x-subject").unwrap();
let resource = req.headers().get("x-resource").unwrap();
let operation = req.headers().get("x-operation").unwrap();
match verify_token(&token, subject, resource, operation) {
Ok(_) => next.run(req).await,
Err(_) => Response::forbidden()
}
}
Event-Driven Systems
Include tokens in event payloads:
{
"event_id": "evt_123",
"type": "order.created",
"data": {
/* ... */
},
"authorization": {
"token": "biscuit_token_here",
"subject": "uri:urn:service:order-processor",
"resource": "orders",
"operation": "create"
}
}
> Performance Considerations
Token Verification Performance
Local verification benchmarks:
- Simple token: ~50μs
- Service chain (3 nodes): ~150μs
- Multi-party (3 signers): ~200μs
Caching Strategies
Cache tokens for repeated operations:
// Token cache for batch operations
struct TokenCache {
tokens: HashMap<(String, String), (String, Instant)>,
}
impl TokenCache {
async fn get_token(&mut self, resource: &str, operation: &str) -> Result<String> {
let key = (resource.to_string(), operation.to_string());
if let Some((token, issued)) = self.tokens.get(&key) {
if issued.elapsed() < Duration::from_secs(240) { // 4 minutes
return Ok(token.clone());
}
}
let token = request_new_token(resource, operation).await?;
self.tokens.insert(key, (token.clone(), Instant::now()));
Ok(token)
}
}
Batch Authorization
Request multiple tokens in parallel:
use futures::future::join_all;
let requests = vec![
client.request_token("users", "read"),
client.request_token("orders", "create"),
client.request_token("inventory", "update"),
];
let tokens = join_all(requests).await;
> Troubleshooting
Common Issues
Token Expired
Error: Token has expired
- Tokens are short-lived by design
- Request new token just before use
- Consider your network latency when setting TTL
Service Chain Verification Failed
Error: Missing attestation from: fraud_detection
- Ensure services attest in correct order
- Verify public keys match configuration
- Check service keypairs are correctly configured
Multi-Party Signature Missing
Error: Required signature from legal_dept not found
- Ensure all required services are reachable
- Verify mTLS credentials work with each service
- Check each service's policy allows the operation
Debug Commands
# Decode and inspect a token
hessra token inspect --token "biscuit_..."
# Test service chain configuration
hessra chain verify \
--token "biscuit_..." \
--chain-config chain.toml
# Validate policy configuration
hessra policy validate --path ./policy-repo
> Best Practices
Token Lifetime
Keep tokens short-lived:
- Default: 5 minutes
- High-security: 30-60 seconds
- Batch operations: Match batch duration
- Never exceed 10 minutes
Request Patterns
- Just-in-time: Request tokens immediately before use
- Least privilege: Request minimum necessary scope
- Single use: Don't reuse tokens across operations
- Fail secure: Deny access on any verification failure
Policy Management
- Version control: Keep policies in git
- Review process: Require approval for policy changes
- Test environments: Validate policies before production
- Audit regularly: Review and prune unused permissions
> Migration Guide
From JWT/OAuth
// Before (JWT)
let claims = decode_jwt(&token)?;
if claims.scopes.contains("users:read") {
// Allow access
}
// After (Hessra)
client.verify_token_local(&token, subject, "users", "read")?;
// Cryptographically verified, offline, with proof
From API Keys
// Before (API Key)
if is_valid_api_key(&request.api_key) {
// Check permissions in database
let perms = db.get_permissions(&request.api_key).await?;
if perms.can_read_users {
// Allow access
}
}
// After (Hessra)
client.verify_token_local(&token, subject, "users", "read")?;
// No database lookup, cryptographically secure
> Next Steps
- Review identity tokens for authentication
- Explore our SDK examples
- Check the API documentation
- Join our community Discord
Ready to implement secure, verifiable authorization? Contact us for enterprise support or visit our GitHub for open source SDKs.