-
Notifications
You must be signed in to change notification settings - Fork 174
Cedarling Design for `authorize_multi_issuer`
The authorize_multi_issuer interface implements a Cedar-based authorization system that processes multiple JWT tokens from various issuers in a single request. The system addresses the current Cedarling limitation of accepting only one token per type by introducing a flexible, extensible architecture that handles arbitrary token mappings without pre-defining entity types.
Complete Interface Structure:
interface AuthorizeMultiIssuerRequest {
tokens: TokenInput[]; // Array of JWT tokens with explicit type mappings
resource?: JSON; // Optional resource being accessed (JSON format)
action?: JSON; // Optional action being performed (JSON format)
context?: JSON; // Optional additional context for policy evaluation (JSON format)
}Key Technical Approach:
- Token Validation: Uses existing Cedarling token validation capabilities
- Dynamic Entity Creation: Creates Cedar entities based on token mapping strings (e.g., "Acme::DolphinToken")
- Individual Token Processing: Processes each token separately without combining or joining
- Secure Field Naming: Uses trusted issuer metadata for predictable, secure token collection naming
- Schema Flexibility: Operates schema-less with Set of String defaults, or with enhanced typing when Cedar schema is defined
Developer sends an Array of tokens from different issuers with different types:
[
{"mapping": "Jans::Access_Token", "payload": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},
{"mapping": "Jans::Id_token", "payload": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."},
{"mapping": "Acme::DolphinToken", "payload": "ey1b6cfMef21084633a77fae9dT1b6cefe..."},
{"mapping": "Microsoft::Access_Token", "payload": "ey84351930d3504880b4cec36c828ff7d7..."},
{"mapping": "Google::Id_token", "payload": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."}
]Note: Each issuer can only provide one token of each type. The authorization request will fail if multiple tokens of the same type from the same issuer are presented, as this is non-deterministic input.
Input: Array of Token Objects
↓
Token Validation (using existing Cedarling capabilities)
↓
Dynamic Cedar Entity Creation (individual tokens)
↓
Token Collection Assembly (no joining)
↓
Policy Evaluation
↓
Authorization Decision
- Token Validator: Uses existing Cedarling token validation capabilities
- Dynamic Entity Factory: Creates Cedar entities for any token mapping type
- Token Collection Builder: Assembles individual tokens into queryable collections
- Policy Engine: Evaluates Cedar policies against individual tokens
- Response Handler: Formats authorization decisions and error messages
interface TokenInput {
mapping: string; // e.g., "Jans::Access_Token", "Acme::DolphinToken"
payload: string; // JWT token string
}
interface AuthorizeMultiIssuerRequest {
tokens: TokenInput[];
resource?: JSON; // Optional resource being accessed (JSON format)
action?: JSON; // Optional action being performed (JSON format)
context?: JSON; // Optional additional context for policy evaluation (JSON format)
}Responsibilities:
- Use existing Cedarling token validation capabilities
- Handle validation failures gracefully -- ignore invalid tokens
- Extract raw claims without interpretation
- Validate that multiple tokens of the same type from the same issuer are rejected
Validation Process:
- Leverages existing Cedarling token validation (signature, content, status checks)
- Validate there are no "Non-Deterministic Tokens": Only one token per type per issuer is allowed
- Ignore "Failed Tokens": Only tokens from trusted issuers are processed -- any non validated tokens are ignored but should not stop processing
- Use discovered issuer information for naming and validation
Failure Handling:
- Log specific validation failures with detailed explanations
- Continue processing remaining tokens in the event of failed tokens
- Exclude failed tokens from Cedar entity mapping and memory
- Do not block execution for Failed Tokens
Responsibilities:
- Create Cedar entities for any token mapping type
- Convert JWT claims to Cedar-compatible attributes
- Handle unknown/custom token types dynamically
- Properly map multi-valued claims to Cedar Sets
Key Features:
- No pre-defined entity types - creates entities based on mapping string
- Preserves all JWT claims as entity tags with appropriate types
- Converts multi-valued claims (scope, aud, etc.) to Cedar Sets
- Stores single-valued claims as appropriate primitive types (String, Long, Boolean)
- Handles arbitrary claim structures
- Creates individual token entities with predictable field names
Claim Type Mapping:
- Without Schema: All claims → Cedar Set of String (provides consistent interface)
- With Schema: Claims can use specific types (DateTime, Long, Boolean, etc.) as defined in the schema
- Conversion: All JWT claim values are converted to strings and stored in Sets, even single values
Responsibilities:
- Organize individual tokens into collections by issuer and token type
- Create queryable token collections with predictable naming
- Handle validation failures during collection assembly
Key Features:
- Creates individual entities for each token
- Uses simple naming convention:
{issuer}_{token_type} - Creates predictable field names for ergonomic policy syntax
- Continues processing with valid tokens even if some tokens fail validation
- Maintains clear separation between individual tokens
The system creates Cedar entities dynamically based on token mapping without requiring predefined entity types. This enables support for arbitrary token types like "Acme::DolphinToken" while maintaining flexibility for different deployment scenarios.
Schema Flexibility:
- Optional Schema: System operates without predefined schema, storing all claims as Sets of strings
- Enhanced Schema Mode: When Cedar schema is defined, enables proper data type casting (DateTime, Long, Boolean)
- Migration Path: Start schema-less for rapid development, add schema later for production type safety
Each token becomes a Cedar entity with this flexible structure:
entity Token = {
"token_type": String, // e.g., "Jans::Access_Token", "Acme::DolphinToken"
"jti": String, // Unique token identifier for instance
"issuer": String, // JWT issuer claim
"validated_at": Long, // Timestamp when token was validated
"exp": Long
} tags String;
Accessing Token Claims: JWT claims are stored as tags on the Token entity. By default, all claims are stored as Sets of strings to provide a consistent interface for developers, regardless of whether the claim has zero, one, or multiple values:
All Claims (stored as Set of String):
-
context.tokens.acme_access_token.hasTag("scope") && context.tokens.acme_access_token.getTag("scope").contains("read:profile")- Check if scope set contains specific value -
context.tokens.acme_access_token.hasTag("aud") && context.tokens.acme_access_token.getTag("aud").contains("my-client-id")- Check if audience set contains client ID -
context.tokens.acme_access_token.hasTag("sub") && context.tokens.acme_access_token.getTag("sub").contains("user123")- Check subject (single-value set) -
context.tokens.acme_access_token.hasTag("location") && context.tokens.acme_access_token.getTag("location").contains("miami")- Check location claim (single-value set)
Note: If a Cedar schema is defined for the Token entity, specific claim type mappings can be used (e.g., DateTime for time-based claims), but without a schema, all claims default to Set of String for consistency.
The Cedar context contains individual tokens using the domain name of the issuer and token type from the Cedar mapping to create predictable naming:
entity Tokens = {
// Format: {trusted_issuer_name}_{token_type_simplified}
"acme_access_token": Token, // Individual access token from Acme
"acme_id_token": Token, // Individual ID token from Acme
"dolphin_access_token": Token, // Individual access token from Dolphin
"dolphin_acme_dolphin_token": Token, // Individual Acme::DolphinToken from Dolphin
"total_token_count": Long,
};
The Token Collection Builder creates predictable field names using a deterministic algorithm that combines issuer domain and token type information:
Pattern: {issuer}_{token_type}
-
Look up issuer in Trusted Issuer metadata and use the
namefield as the issuer prefix -
If no
namefield exists, use the hostname from the JWTissclaim (dropping protocol and path) -
Convert to lowercase and replace spaces/special chars with underscores for Cedar compatibility
- Remove protocol and path, replace dots with underscores, convert to lowercase
- Extract from mapping field (e.g., "Jans::Access_Token", "Acme::DolphinToken")
- Split by namespace separator ("::")
-
Process each part:
- Convert to lowercase
- Keep underscores in token type names
- Use only the last entity name
| JWT Issuer | Trusted Issuer Name | Token Mapping | Issuer Simplified | Type Simplified | Final Key |
|---|---|---|---|---|---|
https://idp.acme.com/auth |
"Acme" |
Jans::Access_Token |
acme |
access_token |
acme_access_token |
https://idp.acme.com/auth |
"Acme" |
Jans::Id_Token |
acme |
id_token |
acme_id_token |
https://idp.dolphin.sea/auth |
"Dolphin" |
Acme::DolphinToken |
dolphin |
acme_dolphin_token |
dolphin_acme_dolphin_token |
https://login.microsoftonline.com/tenant |
"Microsoft" |
Jans::Access_Token |
microsoft |
access_token |
microsoft_access_token |
https://auth.company.internal:8443/oauth |
"Company" |
Custom::EmployeeToken |
company |
custom_employee_token |
company_custom_employee_token |
- Since each issuer can only provide one token of each type, collisions are prevented by design
- System rejects requests with multiple tokens of the same type from the same issuer
- Log validation events for debugging and monitoring
Here are Cedar policy examples using the flattened structure approach for ergonomic policy syntax:
// Query tokens from specific issuer with claim check
permit(
principal,
action == Acme::Action::"GetFood",
resource in Acme::Resources::"ApprovedDolphinFoods"
) when {
context has tokens.dolphin_access_token &&
context.tokens.dolphin_access_token.hasTag("location") &&
context.tokens.dolphin_access_token.getTag("location").contains("miami")
};
// Query specific token type with scope
permit(
principal,
action == Acme::Action::"ReadProfile",
resource in Acme::Resource::"WikiPages"
) when {
context has tokens.acme_access_token &&
context.tokens.acme_access_token.hasTag("scope") &&
context.tokens.acme_access_token.getTag("scope").contains("read:wiki")
};
// Allow a token from different issuers
permit(
principal,
action in TradeAssociation::Action::"Vote",
resource in TradeAssociation::Resource::"Elections"
) when {
// Check if any access token has write:documents scope
(context has tokens.acme_access_token &&
context.tokens.acme_access_token.hasTag("scope") &&
context.tokens.acme_access_token.getTag("scope").contains("trade_association_vote")) ||
(context has tokens.nexo_access_token &&
context.tokens.nexo_access_token.hasTag("scope") &&
context.tokens.nexo_access_token.getTag("scope").contains("trade_association_vote"))
};
// Handle arbitrary token mappings like Acme::DolphinToken
permit(
principal,
action == Acme::Action::"SwimWithOrca",
resource == Acme::Resource::"MiamiAcquarium"
) when {
context has tokens.dolphin_acme_dolphin_token &&
context.tokens.dolphin_acme_dolphin_token.hasTag("waiver") &&
context.tokens.dolphin_acme_dolphin_token.getTag("waiver").contains("signed")
};
// Require tokens from multiple specific issuers
permit(
principal,
action in TradeAssociation::Action::"Vote",
resource in TradeAssociation::Resource::"Elections"
) when {
// Check if any access token has write:documents scope
(context has tokens.trade_association_access_token &&
context.tokens.trade_association_access_token.hasTag("member_status") &&
context.tokens.trade_association_access_token.getTag("member_status").contains("Corporate Member")) &&
(context has tokens.nexo_access_token &&
context.tokens.nexo_access_token.hasTag("scope") &&
context.tokens.nexo_access_token.getTag("scope").contains("trade_association_rep"))
};
// Check if user has required clearance level (numeric)
permit(
principal,
action == Security::Action::"CopyOffite",
resource in Security::Resources:"ClassifiedData"
) when {
context has tokens.gov_access_token &&
context.tokens.gov_access_token.hasTag("clearance_levels") &&
context.tokens.gov_access_token.getTag("clearance_levels").containsAny([5, 6, 7])
};
// Check if user can access specific resource
permit(
principal,
action == Banking::Action::"ViewAccountBalance",
resource == Banking::Account::"12345"
) when {
context has tokens.bank_access_token &&
context.tokens.bank_access_token.hasTag("account_id") &&
context.tokens.bank_access_token.getTag("account_id").contains(12345)
};
// Check minimum security score requirement
permit(
principal,
action == Security::Action::"ProcessPayment",
resource in Security::Resources:"CorporateLegalDocuments"
) when {
context has tokens.bank_access_token &&
context.tokens.bank_access_token.hasTag("security_score") &&
context.tokens.bank_access_token.getTag("security_score") >= 85
};
- Individual Failed Tokens do not stop processing of other tokens
- Non-Deterministic Tokens should throw an error and stop processing.
- Policy evaluation proceeds with available valid tokens
- Clear error reporting indicates which tokens were excluded and why
-
Token Validator Tests
- Test integration with existing Cedarling validation
- Test issuer constraint validation (same type from different issuers)
- Test logging output for various failure scenarios
-
Dynamic Entity Factory Tests
- Test entity creation for standard token types
- Test entity creation for custom token mappings
- Test claim conversion to Cedar-compatible attributes
-
Token Collection Builder Tests
- Test individual token entity creation
- Test naming convention algorithm
- Test handling of validation failures during collection assembly
-
End-to-End Authorization Flow
- Test complete flow from token input to authorization decision
- Test with mixed valid and invalid tokens
- Test with multiple issuers and token types
-
Policy Evaluation Tests
- Test policy evaluation with individual token collection structure
- Test cross-token validation scenarios
- Test custom token type policy evaluation
-
Scalability Tests
- Test with large numbers of tokens
- Test parallel validation processing
- Test memory usage with individual token processing
-
Validation Performance
- Test existing Cedarling validation integration timing
- Test impact of validation failures on overall performance