JWT Configuration Guide
Overview
Sorcha uses JSON Web Tokens (JWT) for authentication and authorization across all microservices. The JWT configuration ensures that tokens issued by the Tenant Service can be validated by all other services in the installation.
Key Concepts
Installation Name
The InstallationName is a unique identifier for your Sorcha deployment (e.g., localhost, dev.sorcha.io, prod.sorcha.io). It's used to automatically derive the JWT issuer and audience values.
Issuer (iss claim)
The issuer identifies who created and signed the token. In Sorcha, the Tenant Service is always the issuer.
Audience (aud claim)
The audience identifies who the token is intended for. All services in the same installation should accept tokens with the installation's audience.
Default Configuration
Docker Compose (Local Development)
The default docker-compose setup uses:
- Installation Name:
localhost - Issuer:
http://localhost(derived from InstallationName) - Audience:
http://localhost(derived from InstallationName) - Signing Key: Shared base64-encoded key (auto-generated or from environment)
Configuration Hierarchy
JWT settings are resolved in this priority order:
- Explicit Configuration -
JwtSettings:IssuerandJwtSettings:Audiencein appsettings or environment variables - Derived from InstallationName - If not explicitly set, uses
http://{InstallationName} - Fallback Defaults -
https://tenant.sorcha.io(issuer) andhttps://api.sorcha.io(audience)
Environment Variables
Required for Docker Compose
environment:
# Installation identifier (required for automatic issuer/audience derivation)
JwtSettings__InstallationName: ${INSTALLATION_NAME:-localhost}
# Shared signing key (required - must be same across all services)
JwtSettings__SigningKey: ${JWT_SIGNING_KEY:-<base64-key>}Optional Overrides
environment:
# Explicit issuer (overrides InstallationName derivation)
JwtSettings__Issuer: "https://auth.example.com"
# Explicit audience (overrides InstallationName derivation)
JwtSettings__Audience__0: "https://api.example.com"
JwtSettings__Audience__1: "https://app.example.com"
# Token lifetimes
JwtSettings__AccessTokenLifetimeMinutes: 60
JwtSettings__RefreshTokenLifetimeHours: 24
JwtSettings__ServiceTokenLifetimeHours: 8
# Validation settings
JwtSettings__ValidateIssuer: true
JwtSettings__ValidateAudience: true
JwtSettings__ValidateIssuerSigningKey: true
JwtSettings__ValidateLifetime: true
JwtSettings__ClockSkewMinutes: 5Configuration by Environment
Local Development (Docker Compose)
File: docker-compose.yml
# Shared JWT configuration for all services
x-jwt-env: &jwt-env
JwtSettings__InstallationName: ${INSTALLATION_NAME:-localhost}
JwtSettings__SigningKey: ${JWT_SIGNING_KEY:-<default-dev-key>}Result:
- Issuer:
http://localhost - Audience:
http://localhost
.NET Aspire (Local Development)
File: src/Apps/Sorcha.AppHost/AppHost.cs
builder.AddProject<Projects.Sorcha_Tenant_Service>("tenant-service")
.WithEnvironment("JwtSettings__InstallationName", "localhost");Result:
- Issuer:
http://localhost - Audience:
http://localhost
Staging/Production
Option 1: Use Installation Name
environment:
JwtSettings__InstallationName: "staging.sorcha.io"
JwtSettings__SigningKey: ${JWT_SIGNING_KEY} # From Azure Key VaultResult:
- Issuer:
http://staging.sorcha.io - Audience:
http://staging.sorcha.io
Option 2: Explicit Configuration
environment:
JwtSettings__Issuer: "https://auth.sorcha.io"
JwtSettings__Audience__0: "https://api.sorcha.io"
JwtSettings__SigningKey: ${JWT_SIGNING_KEY} # From Azure Key VaultResult:
- Issuer:
https://auth.sorcha.io - Audience:
https://api.sorcha.io
Signing Key Management
Development
Auto-Generated Key:
- Services auto-generate a shared development key stored in
%LOCALAPPDATA%\Sorcha\dev-jwt-signing-key.txt - Key is persisted across service restarts
- DO NOT use in production
Docker Compose:
- Uses a default base64-encoded key specified in
docker-compose.yml - Can be overridden via
.envfile:JWT_SIGNING_KEY=your-base64-key-here
Production
REQUIRED: Provide signing key via:
Environment Variable (recommended):
bashexport JwtSettings__SigningKey="<base64-encoded-key>"Azure Key Vault (best for production):
yamlenvironment: JwtSettings__SigningKeySource: "AzureKeyVault" JwtSettings__KeyVaultUri: "https://your-vault.vault.azure.net/" JwtSettings__SigningKeyName: "sorcha-jwt-signing-key"AWS Secrets Manager:
yamlenvironment: JwtSettings__SigningKeySource: "AwsSecretsManager" JwtSettings__SecretId: "sorcha/jwt-signing-key"
Token Structure
User Token (Access Token)
{
"sub": "00000000-0000-0000-0001-000000000001",
"email": "admin@sorcha.local",
"jti": "unique-token-id",
"name": "System Administrator",
"org_id": "00000000-0000-0000-0000-000000000001",
"org_name": "Sorcha Local",
"token_type": "user",
"role": ["Administrator", "SystemAdmin"],
"iss": "http://localhost",
"aud": "http://localhost",
"exp": 1735858000,
"iat": 1735854400
}Service Token
{
"sub": "00000000-0000-0000-0002-000000000001",
"jti": "unique-token-id",
"client_id": "service-blueprint",
"service_name": "Blueprint Service",
"token_type": "service",
"scope": ["blueprints:read", "blueprints:write"],
"iss": "http://localhost",
"aud": "http://localhost",
"exp": 1735883200,
"iat": 1735854400
}Troubleshooting
Authentication Fails: "Invalid audience"
Cause: Issuer or audience mismatch between token issuer (Tenant Service) and validator (other services).
Solution:
Check
InstallationNameis identical across all services:bashdocker-compose config | grep InstallationNameVerify all services use the same configuration:
bashdocker logs sorcha-tenant-service 2>&1 | grep -i "jwt\|issuer" docker logs sorcha-wallet-service 2>&1 | grep -i "jwt\|issuer"Restart all services after configuration change:
bashdocker-compose down docker-compose up -d
Authentication Fails: "Invalid signature"
Cause: Services are using different signing keys.
Solution:
Ensure all services use the same
JwtSettings__SigningKey:bashdocker-compose config | grep SigningKeyRebuild services to pick up new key:
bashdocker-compose build docker-compose up -d
Tokens Expire Immediately
Cause: Clock skew between services or token lifetime too short.
Solution:
Increase clock skew tolerance:
yamlJwtSettings__ClockSkewMinutes: 10Adjust token lifetimes:
yamlJwtSettings__AccessTokenLifetimeMinutes: 120 JwtSettings__RefreshTokenLifetimeHours: 48
Best Practices
Local Development
✅ DO:
- Use
InstallationNameapproach for simplicity - Use the default development signing key
- Set
ValidateAudience: trueto catch configuration issues early
❌ DON'T:
- Disable token validation
- Use production keys in development
Production
✅ DO:
- Store signing key in Azure Key Vault or AWS Secrets Manager
- Use HTTPS for issuer and audience URLs
- Enable all validation options
- Rotate signing keys periodically
- Use separate installations for staging/production
❌ DON'T:
- Use auto-generated development keys
- Commit signing keys to source control
- Disable signature validation
- Use HTTP in production
Default Credentials
For local development, the Tenant Service creates a default admin user:
- Email:
admin@sorcha.local - Password:
Dev_Pass_2025! - Organization:
Sorcha Local
⚠️ Change these credentials immediately in production!
Testing JWT Configuration
Using Sorcha CLI
# Configure CLI for local Docker
sorcha config list
# Login with default credentials
sorcha auth login
# Email: admin@sorcha.local
# Password: Dev_Pass_2025!
# Check token
sorcha auth statusManual Testing
# Get token
curl -X POST http://localhost/api/service-auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=admin@sorcha.local&password=Dev_Pass_2025!&client_id=sorcha-cli"
# Decode token (using jwt.io or jwt-cli)
echo "<access_token>" | jwt decode -
# Verify issuer and audience match your configuration