Getting Started with Hessra Identity
Hessra Identity tokens provide a modern approach to machine authentication. Unlike traditional API keys or JWTs, identity tokens prove who you are without granting any permissions. They're cryptographically delegatable, letting you create hierarchical identity chains without contacting a central authority.
When to Use Identity Tokens
Identity tokens excel in scenarios where you need:
Opaque Token Replacement
- OAuth Refresh Tokens: Instead of long-lived tokens with broad permissions
- Static API Keys: Replace copy-pasted secrets with time-bounded, delegatable identities
- Service Accounts: Give services identity without ambient authority
Ephemeral Identities
- AI Agents and Sub-agents: Create delegation chains for autonomous systems
- CI/CD Pipeline Jobs: Scope identity to specific build or deployment tasks
- Temporary Access: Grant contractors or tools time-limited identity
Core Concepts
Identity vs Authorization
- Identity Token: Proves WHO you are (like a passport)
- Authorization Token: Grants WHAT you can do (like a ticket)
Identity tokens carry no permissions. They're used to authenticate when requesting authorization tokens. You can also use Hessra identity tokens without Hessra authorization. This is a great option if you already have a robust self-made authorization system or already rely on a policy engine like SpiceDB or OPA.
Delegation Chains
Identity tokens can be delegated offline, creating a verifiable chain:
alice → alice:agent → alice:agent:worker
Each level can only act as itself and delegate further down. The worker cannot impersonate the agent or alice.
Domain-Restricted Identities
For end-users or multi-tenant scenarios, realm identities can mint domain-restricted tokens that:
- Cannot create sub-identities or delegate
- Get permissions from server-configured roles
- Are bound to a specific domain context
See Domain-Restricted Identities for details.
Installation
Hessra CLI
# macOS/Linux
curl -L https://releases.hessra.net/cli/latest | bash
# Or with cargo
cargo install hessra
Rust SDK
[dependencies]
hessra-sdk = "0.10.0"
Python SDK
pip install hessra-py
Basic Usage
1. Obtain Your First Identity Token
Authenticate using mTLS certificates to get your root identity:
# Using the CLI
hessra identity authenticate \
--server auth.your-domain.com \
--cert client.crt \
--key client.key \
--ca ca.crt \
--ttl 86400 \
--save-as my-identity
# Response:
# ✓ Identity token saved as 'my-identity'
# Identity: urn:hessra:alice
# Expires in: 86400 seconds
Or programmatically with Rust:
use hessra_sdk::{Hessra, Protocol};
let mut client = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(client_cert)
.mtls_key(client_key)
.server_ca(ca_cert)
.build()?;
// Request identity token (no resource/operation needed)
let response = client.request_identity_token(None).await?;
if let Some(token) = response.token {
println!("Identity: {}", response.identity.unwrap());
println!("Token received: {}...", &token[..50]);
}
2. Verify Identity Tokens
Verification happens locally without network calls:
// Verify the token matches the claimed identity
client.verify_identity_token_local(
&token,
"urn:hessra:alice"
)?;
With the CLI:
hessra identity verify \
--token-name my-identity \
--identity "urn:hessra:alice"
# ✓ Identity token is valid
3. Delegate Identity
Create a more restricted identity token for a sub-component:
# Create delegated identity for an agent
hessra identity delegate \
--from-token my-identity \
--identity "urn:hessra:alice:data-processor" \
--ttl 3600 \
--save-as agent-identity
In Rust:
// Attenuate the token to a new identity
let delegated_token = client.attenuate_identity_token(
&parent_token,
"urn:hessra:alice:data-processor",
3600 // 1 hour
)?;
// The delegated token can only be used as alice:data-processor
client.verify_identity_token_local(
&delegated_token,
"urn:hessra:alice:data-processor"
)?;
4. Use Identity for Authorization
Once you have an identity token, use it instead of mTLS for requesting authorization:
// Create client with identity token instead of mTLS
let client = Hessra::builder()
.base_url("auth.your-domain.com")
.identity_token(&identity_token) // Use identity token
.server_ca(ca_cert) // Still need CA for server verification
.build()?;
// Request authorization token
let auth_token = client
.request_token("database", "read")
.await?;
Python example:
import hessra_py
# Build client without mTLS certificates
client = (
hessra_py.HessraClient.builder()
.base_url("auth.your-domain.com")
.server_ca(ca_cert)
.build()
)
# Use identity token for authentication
auth_token = client.request_token_with_identity(
resource="database",
operation="read",
identity_token=identity_token
)
Advanced Patterns
Hierarchical Agent Systems
Build complex delegation hierarchies for AI agents:
class AgentOrchestrator:
def __init__(self, base_identity_token):
self.identity_token = base_identity_token
self.identity = "urn:hessra:system:orchestrator"
def spawn_agent(self, agent_type, task_id):
# Create agent-specific identity
agent_identity = f"{self.identity}:{agent_type}:{task_id}"
# Delegate identity with appropriate TTL
agent_token = self.delegate_identity(
agent_identity,
ttl=300 # 5 minutes for task
)
# Spawn agent with its identity
if agent_type == "analyzer":
return AnalyzerAgent(agent_identity, agent_token)
elif agent_type == "executor":
return ExecutorAgent(agent_identity, agent_token)
Pipeline Identity Scoping
Scope CI/CD pipeline identities to specific stages:
# .github/workflows/deploy.yml
steps:
- name: Create build identity
run: |
hessra identity delegate \
--from-token ${{ secrets.PIPELINE_IDENTITY }} \
--identity "urn:hessra:ci:${{ github.run_id }}:build" \
--ttl 600 \
--save-as build-identity
- name: Build with scoped identity
run: |
export HESSRA_IDENTITY=$(cat build-identity)
./build.sh
- name: Create deploy identity
run: |
hessra identity delegate \
--from-token ${{ secrets.PIPELINE_IDENTITY }} \
--identity "urn:hessra:ci:${{ github.run_id }}:deploy" \
--ttl 300 \
--save-as deploy-identity
Refresh Patterns
Identity tokens can be refreshed before expiration:
// Check if token needs refresh (< 10 minutes remaining)
if token_expires_in < 600 {
let refreshed = client
.refresh_identity_token(¤t_token)
.await?;
// Update stored token
update_token_storage(refreshed.token);
}
Delegation Policies
Control what delegated identities can do using embedded Datalog policies:
// When creating a delegated token, add constraints
let restricted_token = client.attenuate_identity_token_with_policy(
&parent_token,
"urn:hessra:alice:reader",
3600,
// This delegated identity can only request read operations
"check if operation($op), $op == \"read\";"
)?;
Configuring Identity Delegation with GitOps
Hessra uses a GitOps approach for managing identity and authorization policies. Your configuration lives in TOML files in a git repository, providing versioned, auditable control over who can authenticate and what delegations are allowed.
Setting Up Client Identity Permissions
In your clients.toml, define which identities can authenticate and use identity tokens:
# CA certificates that can authenticate clients
[cacerts]
cert_chain = '''-----BEGIN CERTIFICATE-----
...your CA certificates...
-----END CERTIFICATE-----'''
# Define a root identity that can use identity tokens
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:company:alice" }
resources = [
{ name = "production_api", operations = ["read", "write"] },
{ name = "staging_api", operations = ["read", "write"] }
]
identity_token_enabled = true
identity_token_duration = 28800 # 8 hours
# Define a delegated identity with reduced permissions
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:company:alice:agent1" }
resources = [
{ name = "production_api", operations = ["read"] } # Read-only access
]
identity_token_enabled = true
identity_token_duration = 3600 # 1 hour - shorter lived
Controlling Delegation Chains
The configuration automatically validates delegation hierarchies. In the example above:
alicecan authenticate with mTLS and get an 8-hour identity tokenalicecan delegate toalice:agent1alice:agent1automatically has reduced permissions (read-only)alice:agent1gets shorter-lived tokens (1 hour)
Any identity not defined in the configuration won't be accepted by the authorization service, even if cryptographically valid. This gives you central control over your delegation trees.
GitOps Workflow
- Version Control: All changes to
clients.tomlare tracked in git - Review Process: Policy changes go through PR review
- Automatic Sync: The authorization service pulls updates from your repository
- Audit Trail: Complete history of who changed what and when
This approach ensures that identity delegation isn't just cryptographically secure but also organizationally controlled.
Domain-Restricted Identities
Domain-restricted identity tokens bind an identity to a specific domain, preventing the token from being used outside its intended context. Unlike delegatable realm identities, domain-restricted identities cannot mint new identities or create delegation chains.
When to Use Domain-Restricted Identities
Domain-restricted identities are ideal for:
- Multi-Tenant SaaS: Scope user identities to their tenant domain
- Role-Based Access: Assign permissions through server-configured roles
- End-User Tokens: Issue tokens to users who shouldn't be able to delegate further
- Service Boundaries: Restrict identities to specific API domains
How Domain Restrictions Work
- A realm identity (authenticated via mTLS or identity token) mints domain-restricted tokens for subjects within its domain
- The minted token is bound to the realm's domain and cannot create sub-domains
- Permissions are determined by roles configured on the authorization server
- Users get either the default role or an explicitly assigned role
Creating Domain-Restricted Identities
Using the CLI
# Realm identity mints a domain-restricted token for a user
hessra identity mint \
--subject "urn:hessra:mycompany:user123" \
--ttl 3600 \
--server auth.your-domain.com \
--cert realm.crt \
--key realm.key
Using the Rust SDK
use hessra_sdk::{Hessra, Protocol};
// Setup with realm identity (authenticated via mTLS in this example)
let mut realm_sdk = Hessra::builder()
.base_url("auth.your-domain.com")
.mtls_cert(realm_cert)
.mtls_key(realm_key)
.server_ca(ca_cert)
.build()?;
realm_sdk.setup().await?;
// Mint a domain-restricted token for a user
let response = realm_sdk
.mint_domain_restricted_identity_token(
"urn:hessra:mycompany:user123".to_string(),
Some(3600) // 1 hour TTL
)
.await?;
let user_token = response.token.unwrap();
println!("Identity: {}", response.identity.unwrap());
Using the Python SDK
import hessra_py
# Setup with realm identity (authenticated via mTLS in this example)
client = (
hessra_py.HessraClient.builder()
.base_url("auth.your-domain.com")
.mtls_cert(realm_cert)
.mtls_key(realm_key)
.server_ca(ca_cert)
.build()
)
client = client.setup_new()
# Mint a domain-restricted token for a user
response = client.mint_domain_restricted_identity_token(
subject="urn:hessra:mycompany:user123",
duration=3600 # 1 hour
)
user_token = response.token
print(f"Identity: {response.identity}")
Using Domain-Restricted Tokens for Authorization
Once you have a domain-restricted identity token, use it to request authorization tokens. The optional domain parameter enables enhanced verification:
// Create client WITHOUT mTLS (using identity token)
let user_sdk = Hessra::builder()
.base_url("auth.your-domain.com")
.server_ca(ca_cert)
.build()?;
// Request authorization with domain context
let auth_response = user_sdk
.request_token_with_identity(
"protected-resource",
"read",
&user_token,
Some("urn:hessra:mycompany".to_string()) // Domain context
)
.await?;
The domain parameter tells the server to verify that the identity's subject belongs to the specified domain using ensure_subject_in_domain() verification.
Role-Based Permissions
Domain-restricted identities get their permissions from server-configured roles:
| Role Type | Description | Example |
|---|---|---|
| Default Role | Applied when no explicit assignment exists | member role with read-only access |
| Explicit Role | Assigned to specific subjects in config | admin role with full access |
Configure roles in your authorization server's clients.toml:
# Define roles for a realm identity's domain
[[clients]]
identifier = { type = "san_uri", value = "uri:urn:mycompany" }
identity_token_enabled = true
# Default role for domain members
[clients.domain.default_role]
name = "member"
resources = [
{ name = "api", operations = ["read"] }
]
# Explicit role assignments
[[clients.domain.role_assignments]]
subject = "uri:urn:mycompany:admin-user"
role = "admin"
[[clients.domain.roles]]
name = "admin"
resources = [
{ name = "api", operations = ["read", "write", "delete"] }
]
Key Differences from Delegatable Identities
| Feature | Delegatable Identity | Domain-Restricted Identity |
|---|---|---|
| Can create sub-identities | Yes | No |
| Permission source | Inherited from parent | Server-configured roles |
| How to mint | Offline delegation from parent | Server request (mTLS or identity token) |
| Use case | Service hierarchies, agents | End users, tenants |
Troubleshooting Domain Restrictions
Token rejected with domain mismatch
Error: Subject not in domain
- Ensure the subject follows the domain hierarchy (e.g.,
urn:hessra:domain:subject) - Verify the domain parameter matches the token's bound domain
Permission denied for domain-restricted identity
Error: Operation not permitted for role
- Check the role assignment in your server configuration
- Verify the default role has the required permissions
- Check if the subject has an explicit role assignment that differs from default
Security Best Practices
Token Lifetime Guidelines
Balance security with operational needs:
| Token Type | Recommended TTL | Use Case |
|---|---|---|
| Human Identity | 8-24 hours | Developer workstations |
| Service Identity | 1-8 hours | Long-running services |
| Agent Identity | 5-60 minutes | Autonomous agents |
| Task Identity | 1-5 minutes | Single operations |
For services and jobs, picking a TTL is basically the halting problem in operating system scheduling. It's impossible to know exactly how long something will take, so pick a safe bound.
Secure Token Storage
Never store tokens in:
- Source code
- Configuration files
- Logs or debug output
Instead use:
- Environment variables (for containers)
- Secure key stores (for persistent storage)
- Memory (for ephemeral use)
Delegation Naming Conventions
Use consistent, hierarchical naming:
urn:hessra:{principal}:{service}:{component}:{subcomponent}
This makes audit logs clear and enables pattern-based policies.
Rotation Strategies
Implement automatic rotation:
// Background task to refresh identity
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(3600)).await;
match client.refresh_identity_token(¤t_token).await {
Ok(new_token) => update_token(new_token),
Err(e) => log::error!("Token refresh failed: {}", e),
}
}
});
Integration Examples
With Authorization Tokens
Identity tokens authenticate you to request authorization:
// Step 1: Get identity token (once per session)
let identity_response = client.request_identity_token(None).await?;
let identity_token = identity_response.token.unwrap();
// Step 2: Use identity for authorization requests
let auth_client = Hessra::builder()
.base_url("auth.your-domain.com")
.identity_token(&identity_token)
.server_ca(ca_cert)
.build()?;
// Step 3: Request specific authorizations as needed
let read_token = auth_client.request_token("database", "read").await?;
let write_token = auth_client.request_token("database", "write").await?;
With Service Chains
Identity tokens work seamlessly with service chain attestation:
// Each service in the chain has its identity
let service_identity = "urn:hessra:services:api-gateway";
let service_token = get_identity_token(service_identity);
// Attest the authorization token at this hop
let attested = client.attest_service_chain_token(
auth_token,
service_identity
)?;
// Forward to next service with both identity and authorization
next_service.call_with_tokens(service_token, attested);
Troubleshooting
Common Issues
Token Verification Fails
Error: Token identity mismatch
- Ensure the identity in verification matches the token's delegated identity
- Check token hasn't expired
- Verify delegation chain is valid
Cannot Delegate Further
Error: Maximum delegation depth reached
- Biscuit tokens have a maximum delegation depth (default: 10)
- Redesign to use flatter hierarchies if hitting limits
Identity Token Rejected
Error: Identity token not accepted for this operation
- Some operations may require mTLS authentication
- Check service configuration for identity token support
Debug Commands
# Inspect a token's details
hessra identity verify --token-name my-identity --verbose
# List all stored tokens
hessra identity list
# Check token expiration
hessra identity verify --token-name my-identity --check-expiry
Next Steps
Now that you understand identity tokens:
- Explore authorization tokens for permission grants
- Learn about service chains for distributed authorization
- Check our SDK documentation for detailed API references
- See GitHub examples for complete implementations
Questions about identity tokens? Contact us at hello@hessra.net or join our community.