Permission Mongo uses JWT (JSON Web Tokens) for authentication. The system supports both RS256 (asymmetric) and HS256 (symmetric) signing algorithms.
Overview
The authentication system:
- Validates JWT tokens from the
Authorization header
- Extracts user identity, tenant ID, and roles from token claims
- Supports custom claims for fine-grained access control
- Integrates with RBAC policies for permission enforcement
Token Structure
A valid JWT token must contain these required claims:
{
"sub": "user-123", // Subject (user ID) - REQUIRED
"tenant_id": "acme-corp", // Tenant identifier
"roles": ["manager", "editor"], // User roles
"iat": 1640000000, // Issued at (Unix timestamp)
"exp": 1640003600, // Expiration (Unix timestamp)
"iss": "your-auth-service", // Issuer (optional)
"aud": ["permission-mongo"] // Audience (optional)
}
Custom claims can also be included:
{
"sub": "user-456",
"tenant_id": "acme-corp",
"roles": ["sales-rep"],
"department": "sales", // Custom claim
"region": "us-west", // Custom claim
"iat": 1640000000,
"exp": 1640003600
}
Source: /home/daytona/workspace/source/pkg/auth/jwt.go:39-49
Configuration
Step 1: Choose Your Algorithm
Decide between RS256 (recommended for production) or HS256:
More secure
Uses public/private key pair
Private key stays with auth service, public key with Permission Mongo
Recommended for production
Simpler setup
Uses shared secret
Secret must be kept secure on both sides
Good for development and internal services
For RS256, you need to provide the public key:
auth:
algorithm: RS256
public_key_file: /path/to/public.pem
# OR provide key directly:
# public_key: |
# -----BEGIN PUBLIC KEY-----
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
# -----END PUBLIC KEY-----
issuer: your-auth-service # Optional: validate issuer
audience: permission-mongo # Optional: validate audience
Generate a key pair for testing:
# Generate private key
openssl genrsa -out private.pem 2048
# Extract public key
openssl rsa -in private.pem -pubout -out public.pem
Source: /home/daytona/workspace/source/pkg/auth/jwt.go:29-37
For HS256, provide a shared secret:
auth:
algorithm: HS256
secret: ${ENV.JWT_SECRET} # Use environment variable
issuer: your-auth-service
audience: permission-mongo
Generate a strong secret:
Token Generation
Your authentication service generates tokens. Here’s an example using Go:
RS256 Token Generation
import (
"crypto/rsa"
"time"
"github.com/golang-jwt/jwt/v5"
)
func generateRS256Token(privateKey *rsa.PrivateKey, userID, tenantID string, roles []string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"tenant_id": tenantID,
"roles": roles,
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "your-auth-service",
"aud": []string{"permission-mongo"},
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
return token.SignedString(privateKey)
}
HS256 Token Generation
func generateHS256Token(secret, userID, tenantID string, roles []string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"tenant_id": tenantID,
"roles": roles,
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
Source: /home/daytona/workspace/source/pkg/auth/jwt_test.go:53-71
Making Authenticated Requests
Include the JWT token in the Authorization header:
curl -X GET https://api.example.com/products \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
The middleware extracts and validates the token:
Source: /home/daytona/workspace/source/pkg/api/middleware.go:21-69
Custom Claims in Policies
Custom JWT claims can be used in RBAC policy conditions:
policies:
sales_leads:
sales_rep:
actions: [read, update]
when: |
doc.region == user.claims.region &&
doc.status == "open"
The user.claims context includes all JWT claims:
// Available in policy expressions:
user.claims.department // "sales"
user.claims.region // "us-west"
user.claims.level // "senior"
Source: /home/daytona/workspace/source/pkg/auth/context.go:101-118
Token Validation
Permission Mongo validates tokens automatically:
Step 1: Signature Validation
Verifies the token was signed by the expected key:
# RS256: Verifies with public key
# HS256: Verifies with shared secret
{
"error": {
"code": "expired_token",
"message": "token has expired"
}
}
Step 3: Issuer & Audience
Validates iss and aud claims if configured:
auth:
issuer: your-auth-service # Must match token's "iss"
audience: permission-mongo # Must be in token's "aud" array
Ensures sub (subject) claim is present:
{
"sub": "user-123" // Required - user identifier
}
Source: /home/daytona/workspace/source/pkg/auth/jwt.go:150-215
Error Responses
Invalid Token
{
"error": {
"code": "invalid_token",
"message": "invalid token"
}
}
Expired Token
{
"error": {
"code": "expired_token",
"message": "token has expired"
}
}
Invalid Signature
{
"error": {
"code": "invalid_token",
"message": "invalid token signature"
}
}
Missing Authorization
{
"error": {
"code": "unauthorized",
"message": "missing authorization header"
}
}
Source: /home/daytona/workspace/source/pkg/api/middleware.go:46-60
Development Mode
For local development, you can bypass authentication:
server:
dev_mode: true
dev_tenant: test-corp
This sets a default admin context without requiring tokens.
Never enable dev_mode in production! It bypasses all authentication.
Source: /home/daytona/workspace/source/pkg/api/middleware.go:107-131
Multi-Tenancy
The tenant_id claim automatically scopes all operations:
{
"sub": "user-123",
"tenant_id": "acme-corp", // All queries filtered by this
"roles": ["admin"]
}
Queries are automatically filtered:
// User queries:
GET /products
// Automatically becomes:
GET /products?tenant_id=acme-corp
Source: /home/daytona/workspace/source/pkg/auth/context.go:16-25
Best Practices
Use RS256 in production for better security
Set short expiration times (15-60 minutes)
Validate issuer and audience claims
Store secrets in environment variables, not config files
Rotate signing keys regularly
Include minimal claims in tokens (roles, tenant_id)
Use refresh tokens for long-lived sessions
Testing Authentication
Create test tokens for development:
func TestCreateProduct(t *testing.T) {
// Generate test token
token := createHS256Token(jwt.MapClaims{
"sub": "user-123",
"tenant_id": "test-corp",
"roles": []string{"admin"},
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour).Unix(),
}, "test-secret")
// Make authenticated request
resp := makeRequest("POST", "/products", token, productData)
assert.Equal(t, 201, resp.StatusCode)
}
Source: /home/daytona/workspace/source/pkg/auth/jwt_test.go:190-233
Next Steps
Multi-Tenancy
Configure tenant isolation and data scoping
RBAC Policies
Define role-based permissions