Auth models (web)

Authentication and authorization patterns for granting agents access to applications on behalf of users.

Why it matters

Auth models determine how safely and effectively agents can act on behalf of users in web applications. Poor auth implementation creates security vulnerabilities, while overly restrictive models limit agent capabilities. The challenge is threefold: secure delegation that doesn't expose credentials, explicit user consent for agent actions, and credential management that handles token refresh, expiry, and revocation across potentially long-running agent sessions.

In traditional web applications, auth sessions are short-lived and tied to direct user interaction. Agents invert this model—they may operate for extended periods, make decisions autonomously, and access multiple services in sequence. This requires auth patterns that support granular permission scoping, auditable delegation trails, and graceful handling of auth state changes (like user logout or token revocation) mid-execution.

Without proper auth models, agents either gain excessive privileges (security risk) or fail unpredictably when credentials expire (reliability risk). The right model balances security, user control, and operational resilience.

Concrete examples

OAuth 2.0 delegation with PKCE: A scheduling agent uses OAuth 2.0 with Proof Key for Code Exchange to access a user's calendar API. The agent redirects the user to the authorization server, receives an authorization code, exchanges it for an access token with limited scope (read/write calendar events only), and stores the refresh token securely. When the access token expires after 1 hour, the agent automatically requests a new one using the refresh token without user intervention. The user can revoke access at any time through the provider's settings.

Session inheritance with scope narrowing: A customer service agent inherits the user's authenticated session but with explicitly narrowed permissions. While the user has full account access, the agent receives a derived session token with read-only access to order history and write access only to support ticket creation. The agent cannot modify payment methods or personal information. This is implemented by the application server issuing a JWT with reduced claims when agent mode is activated.

API key scoping with time-limited grants: A research agent uses time-boxed API keys to access external data sources. When the user initiates a research task, the application generates a scoped API key valid for 4 hours with rate limits of 100 requests per minute. The key is scoped to specific endpoints (search, retrieve) but not to destructive operations (delete, update). The agent includes this key in request headers and monitors the X-RateLimit-Remaining header to avoid throttling.

Multi-service delegation with token exchange: An analytics agent needs access to both a user's CRM and analytics platform. Rather than storing multiple credentials, it uses OAuth 2.0 token exchange (RFC 8693). The agent receives an initial access token for the CRM, then exchanges it with the analytics provider to receive a delegated token. This creates an audit trail showing the CRM authorized the analytics access, not the user directly sharing credentials with both services.

Common pitfalls

Overly broad scopes: Requesting full account access (user:* or admin scope) when the agent only needs specific permissions. An email summarization agent that requests mail.readwrite instead of mail.read can now delete or modify emails. Always request the minimum scope necessary for the agent's specific task. If multiple tasks require different permissions, request scopes incrementally as needed rather than upfront.

Insecure credential storage: Storing refresh tokens or API keys in browser localStorage or unencrypted application state. These are equivalent to passwords and must be protected with the same rigor. Store sensitive tokens server-side with encryption at rest, or use secure, httpOnly cookies for session tokens. Never log tokens in application logs or error messages.

Missing refresh logic: Failing to implement token refresh before expiry, causing agents to fail mid-task with authentication errors. Access tokens typically expire in 1-24 hours. Implement proactive refresh logic that renews tokens when they're 80-90% through their lifetime. Handle refresh failures gracefully by prompting for re-authentication rather than silently failing or retrying indefinitely.

No revocation handling: Agents that don't gracefully handle token revocation. When a user revokes access or logs out, the agent should detect this (via 401 Unauthorized responses or token introspection endpoints) and stop all operations immediately. Continuing to retry with revoked credentials can trigger rate limits or security alerts. Implement exponential backoff and surface revocation status to users.

Confused deputy attacks: An agent receives credentials scoped to one task but uses them for unrelated tasks. For example, an agent authorized to send emails on behalf of a user also uses those credentials to read the user's contacts without explicit consent. Implement task-specific token binding where tokens include metadata about the authorized task and validate this on each use.

Implementation

Authorization flow patterns

For web-based agents, implement the OAuth 2.0 authorization code flow with PKCE for public clients or standard authorization code flow for confidential clients. When the user activates an agent feature requiring external service access, redirect to the provider's authorization endpoint with specific scopes:

GET /authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/agent/callback&
  scope=calendar.events.read+calendar.events.write&
  state=RANDOM_STATE_TOKEN&
  code_challenge=BASE64URL(SHA256(code_verifier))&
  code_challenge_method=S256

After user consent, exchange the authorization code for tokens:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=https://yourapp.com/agent/callback&
client_id=YOUR_CLIENT_ID&
code_verifier=ORIGINAL_CODE_VERIFIER

Store the access token (short-lived, 1-4 hours) in secure server-side session storage and the refresh token (long-lived, 30-90 days) in encrypted database storage. Associate tokens with both the user ID and the specific agent task ID for auditability.

Token management

Implement a token service layer that handles automatic refresh:

class TokenManager {
  async getValidToken(userId: string, provider: string): Promise<string> {
    const token = await this.storage.getToken(userId, provider);

    // Refresh if token expires in less than 5 minutes
    if (token.expiresAt < Date.now() + 5 * 60 * 1000) {
      return await this.refreshToken(userId, provider);
    }

    return token.accessToken;
  }

  async refreshToken(userId: string, provider: string): Promise<string> {
    const token = await this.storage.getToken(userId, provider);

    const response = await fetch(provider.tokenEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: token.refreshToken,
        client_id: this.clientId,
        client_secret: this.clientSecret
      })
    });

    if (!response.ok) {
      if (response.status === 400 || response.status === 401) {
        // Refresh token invalid or revoked
        await this.storage.deleteToken(userId, provider);
        throw new AuthRevocationError('Token revoked, re-authorization required');
      }
      throw new Error('Token refresh failed');
    }

    const newToken = await response.json();
    await this.storage.updateToken(userId, provider, {
      accessToken: newToken.access_token,
      refreshToken: newToken.refresh_token || token.refreshToken,
      expiresAt: Date.now() + (newToken.expires_in * 1000)
    });

    return newToken.access_token;
  }
}

Scope negotiation

Implement dynamic scope requests based on agent capabilities. When an agent task is initiated, analyze the required operations and request minimal scopes:

interface AgentTask {
  type: 'email_summary' | 'calendar_scheduling' | 'data_analysis';
  operations: Operation[];
}

function calculateRequiredScopes(task: AgentTask): string[] {
  const scopeMap = {
    'read_email': ['mail.read'],
    'send_email': ['mail.send'],
    'read_calendar': ['calendar.read'],
    'write_calendar': ['calendar.readwrite'],
    'read_contacts': ['contacts.read']
  };

  const requiredScopes = new Set<string>();

  for (const operation of task.operations) {
    const scopes = scopeMap[operation.type];
    if (scopes) {
      scopes.forEach(scope => requiredScopes.add(scope));
    }
  }

  return Array.from(requiredScopes);
}

Before executing each agent operation, verify that the current token includes the necessary scope:

async function executeOperation(operation: Operation, token: AccessToken) {
  const requiredScopes = getRequiredScopes(operation);
  const tokenScopes = parseScopes(token.scope);

  const missingScopes = requiredScopes.filter(s => !tokenScopes.includes(s));

  if (missingScopes.length > 0) {
    throw new InsufficientScopeError(
      `Operation requires scopes: ${missingScopes.join(', ')}`
    );
  }

  // Proceed with operation
  return await performOperation(operation, token.accessToken);
}

Implement incremental authorization where agents request additional permissions only when needed:

async function executeAgentWorkflow(workflow: Workflow) {
  for (const step of workflow.steps) {
    try {
      await executeStep(step);
    } catch (error) {
      if (error instanceof InsufficientScopeError) {
        // Request additional scope from user
        const approved = await requestIncrementalAuth(error.requiredScopes);
        if (approved) {
          await executeStep(step); // Retry with new permissions
        } else {
          throw new UserDeniedAuthError('User denied additional permissions');
        }
      } else {
        throw error;
      }
    }
  }
}

Key metrics

Auth success rate: Percentage of agent operations that complete without authentication failures. Target: > 99.5%. Measure both initial authorization (should be > 95%, accounting for user denials) and ongoing operations (should be > 99.9%, failures indicate token management issues). Track separately by provider and scope to identify problematic integrations.

Token expiry incidents: Number of agent failures due to expired tokens per 1,000 operations. Target: < 1. High rates indicate inadequate token refresh logic. Monitor time-to-expiry distribution when tokens are used—if operations frequently use tokens with < 5 minutes remaining, refresh logic is too delayed. Track refresh failures separately from usage failures.

Scope creep: Percentage of agent tasks requesting broader permissions than necessary. Target: 0%. Audit by comparing requested scopes against actually used API endpoints. If an agent requests mail.readwrite but never makes POST/PUT/DELETE requests, only GET, it should request mail.read. Implement automated scope analysis in CI/CD to catch scope creep during development.

Token refresh latency: Time between token expiry detection and successful refresh, measured at p50, p95, p99. Target: p95 < 500ms, p99 < 2s. High latency indicates network issues or inefficient refresh flows. Track proactive refreshes (before expiry) separately from reactive refreshes (after 401 response)—proactive should dominate (> 95% of refreshes).

Re-authorization prompts: Number of times users are asked to re-authenticate per session. Target: < 0.1 per session. Excessive prompts indicate refresh token expiry or revocation issues. Differentiate between expected re-auth (user logged out, revoked access) and unexpected (refresh token expired, lost, or not properly persisted).

Related concepts

  • Session scope: Defining boundaries and lifecycle for agent auth sessions
  • Least privilege: Granting minimal necessary permissions to agents
  • Data handling: Secure storage and transmission of auth credentials
  • Instrumentation: Monitoring and logging auth events for security auditing