Skip to content

RFC: User-Delegated Agent Access #518

@lakhansamani

Description

@lakhansamani

RFC: User-Delegated Agent Access

Phase: 4 — AI-Era Auth
Priority: P2 — Medium
Estimated Effort: Medium
Depends on: MCP Auth (#516), Fine-Grained Permissions (#508)


Problem Statement

Users need to authorize AI agents to act on their behalf with limited, revocable scope. When a user asks an AI agent to "manage my calendar" or "process my invoices," the agent needs a token that represents the user's identity but is constrained to only the permissions the user explicitly granted. WorkOS and Clerk both support this pattern.


Proposed Solution

1. Delegated Authorization Flow

Standard OAuth flow with agent-specific scope constraints:

1. Agent initiates OAuth flow on behalf of user:
   GET /authorize?
       client_id={agent_client_id}&
       scope=calendar:read calendar:write&
       response_type=code&
       code_challenge=...

2. User authenticates (if not already logged in)

3. Consent screen shows agent-specific permissions:
   "Calendar Agent wants to:"
   ☑ Read your calendar events
   ☑ Create and modify calendar events
   [Allow for 24 hours]  [Allow for 7 days]  [Deny]

4. User approves → authorization code issued

5. Agent exchanges code for token:
   POST /oauth/token
       grant_type=authorization_code&code=...

6. Token includes user identity + constrained scope:
   {
       "sub": "user_123",
       "client_id": "app_calendar_agent",
       "scope": "calendar:read calendar:write",
       "delegated": true,
       "delegated_at": 1711800000,
       "delegation_expires_at": 1711886400
   }

2. Scope Downscoping

Agent token permissions = intersection of:

  • User's actual permissions
  • Agent's allowed scopes (from Application registration)
  • User-approved scopes (from consent)
func computeDelegatedScopes(userPermissions []string, agentAllowedScopes []string, userApprovedScopes []string) []string {
    // Token can only have permissions that exist in ALL three sets
    return intersect(userPermissions, agentAllowedScopes, userApprovedScopes)
}

If a user's permissions are later reduced (e.g., admin removes a role), the agent's delegated token automatically loses those permissions at validation time.

3. Time-Limited Delegation

Users choose how long the delegation lasts at the consent screen:

Option TTL Use Case
One-time Token valid for single use Quick task
24 hours 86400s Day-long agent task
7 days 604800s Week-long project
30 days 2592000s Ongoing automation
Until revoked No expiry (refresh token based) Persistent agent access

Schema for tracking delegation grants:

type DelegationGrant struct {
    ID             string `json:"id" gorm:"primaryKey;type:char(36)"`
    UserID         string `json:"user_id" gorm:"type:char(36);index:idx_delegation_user"`
    ClientID       string `json:"client_id" gorm:"type:char(36);index:idx_delegation_client"`
    ApplicationID  string `json:"application_id" gorm:"type:char(36)"`
    Scopes         string `json:"scopes" gorm:"type:text"`
    ExpiresAt      int64  `json:"expires_at"`              // 0 = until revoked
    IsActive       bool   `json:"is_active" gorm:"type:bool;default:true"`
    LastUsedAt     int64  `json:"last_used_at"`
    CreatedAt      int64  `json:"created_at" gorm:"autoCreateTime"`
}

4. Revocation

Users can revoke agent access at any time:

User-facing GraphQL API:

type DelegatedAgent {
    id: ID!
    application: Application!
    scopes: [String!]!
    expires_at: Int64
    last_used_at: Int64
    is_active: Boolean!
    created_at: Int64!
}

type Query {
    delegated_agents: [DelegatedAgent!]!     # List agents with active access
}

type Mutation {
    revoke_agent_access(id: ID!): Response!              # Revoke specific delegation
    revoke_all_agent_access: Response!                    # Revoke all agent delegations
}

On revocation:

  1. Mark DelegationGrant.IsActive = false
  2. Invalidate all tokens issued under this grant (delete from memory store)
  3. Audit log: user.agent_access_revoked

5. Active Agent Sessions Dashboard

Users see which agents have active access in their account settings:

Your Connected Agents:
┌─────────────────────────────────────────────────────┐
│ Calendar Agent                                       │
│ Scopes: calendar:read, calendar:write               │
│ Granted: Mar 15, 2026 · Expires: Apr 14, 2026      │
│ Last used: 2 hours ago                              │
│                                         [Revoke]     │
├─────────────────────────────────────────────────────┤
│ Billing Agent                                        │
│ Scopes: invoices:read                               │
│ Granted: Mar 20, 2026 · No expiry                   │
│ Last used: 5 minutes ago                            │
│                                         [Revoke]     │
└─────────────────────────────────────────────────────┘

This is implemented in the web/app/ React frontend and uses the delegated_agents query.

6. Notification on New Agent Access

When a new agent delegation is created, send email notification:

Subject: New agent access granted to your account

"Calendar Agent" was granted access to your account:
- Read your calendar events
- Create and modify calendar events

Expires: April 14, 2026

If you didn't authorize this, revoke access immediately:
[Revoke Access]

CLI Configuration Flags

--enable-delegated-agent-access=true       # Enable user-delegated agent flow
--max-delegation-duration=2592000          # Max delegation TTL in seconds (30 days)
--notify-on-agent-delegation=true          # Email notification on new delegation

Testing Plan

  • Integration test: full delegation flow (authorize → consent → token → use → revoke)
  • Test scope downscoping (intersection of user + agent + approved scopes)
  • Test time-limited delegation expiry
  • Test revocation invalidates all tokens for the grant
  • Test user permission reduction affects delegated token
  • Test delegation listing and active session display
  • Test email notification on new delegation

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions