Describe the feature
Implement token hierarchy where child tokens (subtokens) inherit claims from parent tokens, including MFA claims, for granular authorization with session-level MFA enforcement.
This feature enables:
- Session tokens that spawn subtokens with inherited MFA claims
- Revocation cascade (revoking parent revokes all children)
- Claim degradation rules (children cannot elevate claims)
Current Behavior
- tokensmith issues independent tokens with no relationships
- No concept of token hierarchy or claim inheritance
- Revoking a token doesn't affect related tokens
Desired Behavior
Token Hierarchy Model
Session Token (12hr lifetime, AMR: ["pwd", "otp"], ACR: "urn:mfa:required")
├─ API Token 1 (30 days, inherits AMR/ACR, scopes: ["read"])
├─ API Token 2 (60 days, inherits AMR/ACR, scopes: ["read", "write"])
└─ Short-lived Token (5 min, inherits AMR/ACR, scopes: ["admin"])
Key Properties:
- Parent tokens are typically session tokens (created via session management API)
- Child tokens inherit MFA claims (
amr, acr, auth_time) from parent
- Revocation cascade: Revoking parent invalidates all descendants
- Claim degradation: Children can only subset/restrict claims, never elevate
1. Create Child Token with Claim Inheritance
Endpoint: POST /oauth/token (extend existing endpoint from Issue #33)
Request Body (JSON):
{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "{parent-jwt}",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"parent_token_id": "tok_parent_abc123", // NEW: Explicit parent linkage
"scope": "read", // Child scopes (subset of parent)
"expires_in": 2592000 // 30 days
}
Response (201 Created):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRodW1icHJpbnQifQ...",
"token_type": "Bearer",
"expires_in": 2592000,
"scope": "read",
"token_id": "tok_child_def456",
"parent_token_id": "tok_parent_abc123",
// Inherited MFA claims (read-only, from parent)
"amr": ["pwd", "otp"],
"acr": "urn:mfa:required",
"auth_time": 1719708600,
"session_id": "sess_xyz789"
}
JWT Claims (Child Token):
{
"sub": "user-123",
"token_id": "tok_child_def456",
"parent_id": "tok_parent_abc123", // NEW: Track parent
// Inherited from parent (immutable)
"amr": ["pwd", "otp"],
"acr": "urn:mfa:required",
"auth_time": 1719708600,
"session_id": "sess_xyz789",
// Child-specific
"scopes": ["read"], // Subset of parent's scopes
"exp": 1722300600,
"iat": 1719708600
}
2. Claim Inheritance Rules
✅ Allowed: Claim Degradation
Children can restrict claims:
// Parent
{
"amr": ["pwd", "otp"],
"scopes": ["read", "write", "delete"]
}
// Child (VALID)
{
"amr": ["pwd", "otp"], // Inherited as-is
"scopes": ["read"] // Subset of parent scopes
}
❌ Forbidden: Claim Elevation
Children cannot add claims:
// Parent
{
"amr": ["pwd"],
"scopes": ["read"]
}
// Child (INVALID - validation error)
{
"amr": ["pwd", "otp"], // ❌ CANNOT add new auth methods
"scopes": ["read", "write"] // ❌ CANNOT add new scopes
}
Validation Error Response:
{
"error": "invalid_request",
"error_description": "Child token cannot elevate claims beyond parent. Parent AMR: [pwd], requested AMR: [pwd, otp]"
}
3. Revocation Cascade
Endpoint: DELETE /oauth/token/{token_id} (extend existing endpoint)
Behavior:
- Revoke the target token (add to invalidation list)
- Find all child tokens recursively
- Revoke each child token
- Return count of revoked tokens
Response (200 OK):
{
"revoked_token_id": "tok_parent_abc123",
"revoked_children": [
"tok_child_def456",
"tok_child_ghi789",
"tok_grandchild_jkl012"
],
"total_revoked": 4
}
Implementation:
func (s *TokenService) RevokeToken(ctx context.Context, tokenID string) error {
// Revoke the token
s.invalidateToken(tokenID)
// Find and revoke all children (recursive)
children := s.findChildTokens(tokenID)
for _, child := range children {
s.RevokeToken(ctx, child.ID) // Recursive call
}
return nil
}
4. Token Hierarchy Query
Endpoint: GET /oauth/token/{token_id}/children
Authentication: Requires valid TokenSmith JWT
Authorization: User can view their own token hierarchies OR admin can view any
Response (200 OK):
{
"token_id": "tok_parent_abc123",
"children": [
{
"token_id": "tok_child_def456",
"name": "api-key-1",
"scopes": ["read"],
"created_at": "2026-06-29T14:30:00Z",
"expires_at": "2026-07-29T14:30:00Z",
"revoked_at": null,
"children_count": 0
},
{
"token_id": "tok_child_ghi789",
"name": "api-key-2",
"scopes": ["read", "write"],
"created_at": "2026-06-29T15:00:00Z",
"expires_at": "2026-08-28T15:00:00Z",
"revoked_at": null,
"children_count": 1 // Has grandchildren
}
]
}
Endpoint: GET /oauth/token/{token_id}/tree
Response (200 OK) - Full tree visualization:
{
"token_id": "tok_parent_abc123",
"name": "session-token",
"type": "session",
"amr": ["pwd", "otp"],
"acr": "urn:mfa:required",
"children": [
{
"token_id": "tok_child_def456",
"name": "api-key-1",
"type": "api_key",
"amr": ["pwd", "otp"], // Inherited
"acr": "urn:mfa:required", // Inherited
"children": []
},
{
"token_id": "tok_child_ghi789",
"name": "api-key-2",
"type": "api_key",
"amr": ["pwd", "otp"], // Inherited
"acr": "urn:mfa:required", // Inherited
"children": [
{
"token_id": "tok_grandchild_jkl012",
"name": "subtoken-1",
"type": "subtoken",
"amr": ["pwd", "otp"], // Inherited
"acr": "urn:mfa:required", // Inherited
"children": []
}
]
}
]
}
Why do you want this feature?
Use Cases
-
Session-Based MFA Enforcement (primary use case):
User authenticates with MFA → Session token captures AMR/ACR →
User creates API keys (children) → API keys inherit MFA claims →
Backend enforces "must have MFA" policies on API key usage
-
Granular Revocation:
- Revoke user session → all API keys created from that session are invalidated
- Revoke API key → all subtokens derived from it are invalidated
- Fine-grained control: revoke individual subtokens without affecting parent
-
Audit Trail:
- Track token lineage: "This API key was created during a session with 2FA"
- Compliance: "All tokens in this hierarchy originated from an MFA-authenticated session"
-
Scope Restriction:
- Session token has full scopes
- Create read-only API key (child) for CI/CD
- Create admin API key (child) for operators
- Each inherits MFA claims but with restricted scopes
Implementation Notes
Circular Reference Prevention
Validation at creation:
func (s *TokenService) CreateChildToken(req CreateTokenRequest) error {
// Check for circular references
if s.isDescendantOf(req.ParentTokenID, req.UserID) {
return ErrCircularReference
}
// ... create token
}
func (s *TokenService) isDescendantOf(candidateParent, candidateChild string) bool {
current := candidateParent
for current != "" {
if current == candidateChild {
return true // Circular reference detected
}
current = s.getParentID(current)
}
return false
}
Depth Limits
Prevent excessively deep hierarchies:
const MaxTokenDepth = 5 // Configurable
func (s *TokenService) validateHierarchyDepth(parentID string) error {
depth := s.calculateDepth(parentID)
if depth >= MaxTokenDepth {
return ErrMaxDepthExceeded
}
return nil
}
Acceptance Criteria
Dependencies
- Session Management API (proposed in separate issue) - Session tokens are the typical parent tokens
Related Issues
Code of Conduct
Describe the feature
Implement token hierarchy where child tokens (subtokens) inherit claims from parent tokens, including MFA claims, for granular authorization with session-level MFA enforcement.
This feature enables:
Current Behavior
Desired Behavior
Token Hierarchy Model
Key Properties:
amr,acr,auth_time) from parent1. Create Child Token with Claim Inheritance
Endpoint:
POST /oauth/token(extend existing endpoint from Issue #33)Request Body (JSON):
{ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "subject_token": "{parent-jwt}", "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", "parent_token_id": "tok_parent_abc123", // NEW: Explicit parent linkage "scope": "read", // Child scopes (subset of parent) "expires_in": 2592000 // 30 days }Response (201 Created):
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRodW1icHJpbnQifQ...", "token_type": "Bearer", "expires_in": 2592000, "scope": "read", "token_id": "tok_child_def456", "parent_token_id": "tok_parent_abc123", // Inherited MFA claims (read-only, from parent) "amr": ["pwd", "otp"], "acr": "urn:mfa:required", "auth_time": 1719708600, "session_id": "sess_xyz789" }JWT Claims (Child Token):
{ "sub": "user-123", "token_id": "tok_child_def456", "parent_id": "tok_parent_abc123", // NEW: Track parent // Inherited from parent (immutable) "amr": ["pwd", "otp"], "acr": "urn:mfa:required", "auth_time": 1719708600, "session_id": "sess_xyz789", // Child-specific "scopes": ["read"], // Subset of parent's scopes "exp": 1722300600, "iat": 1719708600 }2. Claim Inheritance Rules
✅ Allowed: Claim Degradation
Children can restrict claims:
❌ Forbidden: Claim Elevation
Children cannot add claims:
Validation Error Response:
{ "error": "invalid_request", "error_description": "Child token cannot elevate claims beyond parent. Parent AMR: [pwd], requested AMR: [pwd, otp]" }3. Revocation Cascade
Endpoint:
DELETE /oauth/token/{token_id}(extend existing endpoint)Behavior:
Response (200 OK):
{ "revoked_token_id": "tok_parent_abc123", "revoked_children": [ "tok_child_def456", "tok_child_ghi789", "tok_grandchild_jkl012" ], "total_revoked": 4 }Implementation:
4. Token Hierarchy Query
Endpoint:
GET /oauth/token/{token_id}/childrenAuthentication: Requires valid TokenSmith JWT
Authorization: User can view their own token hierarchies OR admin can view any
Response (200 OK):
{ "token_id": "tok_parent_abc123", "children": [ { "token_id": "tok_child_def456", "name": "api-key-1", "scopes": ["read"], "created_at": "2026-06-29T14:30:00Z", "expires_at": "2026-07-29T14:30:00Z", "revoked_at": null, "children_count": 0 }, { "token_id": "tok_child_ghi789", "name": "api-key-2", "scopes": ["read", "write"], "created_at": "2026-06-29T15:00:00Z", "expires_at": "2026-08-28T15:00:00Z", "revoked_at": null, "children_count": 1 // Has grandchildren } ] }Endpoint:
GET /oauth/token/{token_id}/treeResponse (200 OK) - Full tree visualization:
{ "token_id": "tok_parent_abc123", "name": "session-token", "type": "session", "amr": ["pwd", "otp"], "acr": "urn:mfa:required", "children": [ { "token_id": "tok_child_def456", "name": "api-key-1", "type": "api_key", "amr": ["pwd", "otp"], // Inherited "acr": "urn:mfa:required", // Inherited "children": [] }, { "token_id": "tok_child_ghi789", "name": "api-key-2", "type": "api_key", "amr": ["pwd", "otp"], // Inherited "acr": "urn:mfa:required", // Inherited "children": [ { "token_id": "tok_grandchild_jkl012", "name": "subtoken-1", "type": "subtoken", "amr": ["pwd", "otp"], // Inherited "acr": "urn:mfa:required", // Inherited "children": [] } ] } ] }Why do you want this feature?
Use Cases
Session-Based MFA Enforcement (primary use case):
Granular Revocation:
Audit Trail:
Scope Restriction:
Implementation Notes
Circular Reference Prevention
Validation at creation:
Depth Limits
Prevent excessively deep hierarchies:
Acceptance Criteria
POST /oauth/tokenacceptsparent_token_idparameteramr,acr,auth_time,session_idfrom parentamrvalues or scopes)DELETE /oauth/token/{id}revokes token and all descendants (cascade)GET /oauth/token/{id}/childrenlists immediate childrenGET /oauth/token/{id}/treereturns full hierarchy treeDependencies
Related Issues
Code of Conduct