How Composio Handles Authentication with Connected Accounts and OAuth Flows

Composio orchestrates OAuth authentication by treating every third-party service as a connected account linked to an auth config, providing SDK methods that handle the entire lifecycle from initiation to token refresh.

Composio provides a type-safe, uniform layer for managing authentication with external APIs. Whether integrating GitHub, Gmail, or custom services, the SDK abstracts OAuth complexity through a consistent connected account model defined in the ComposioHQ/composio repository.

Understanding Composio's Authentication Architecture

Auth Configs and Supported Schemes

Every connection starts with an auth config that defines the authentication scheme. The AuthSchemeTypes enum in ts/packages/core/src/types/authConfigs.types.ts enumerates all supported protocols, including OAuth 1, OAuth 2, API-Key, Basic, Bearer-Token, and Google service accounts.

// ts/packages/core/src/types/authConfigs.types.ts#L9-L24
export enum AuthSchemeTypes {
  OAUTH1 = 'OAUTH1',
  OAUTH2 = 'OAUTH2',
  API_KEY = 'API_KEY',
  BASIC = 'BASIC',
  BEARER_TOKEN = 'BEARER_TOKEN',
  GOOGLE_SERVICE_ACCOUNT = 'GOOGLE_SERVICE_ACCOUNT',
  // ...
}

This centralized definition allows the SDK to handle any provider using the same orchestration logic, regardless of the underlying protocol.

Implementing OAuth Flows with Connected Accounts

Initiating a Connection Request

The ConnectedAccounts class in ts/packages/core/src/models/ConnectedAccounts.ts provides the initiate() method to start a full-stack OAuth flow. This method POSTs to /connected-accounts and returns a ConnectionRequest object containing the redirect URL where users authenticate with the provider.

// ts/packages/core/src/models/ConnectedAccounts.ts#L58-L66
async initiate(
  userId: string,
  authConfigId: string,
  options?: { callbackUrl?: string; redirectUrl?: string }
): Promise<ConnectionRequest> {
  const response = await this.apiClient.post('/connected-accounts', {
    userId,
    authConfigId,
    ...options
  });
  return createConnectionRequest(response.data, this.apiClient);
}

For simpler integrations, the link() method (lines 152-166 in the same file) creates a lightweight "Connect-Link" flow via POST /link/create. This alternative generates a pre-authenticated URL that can be embedded directly in UI elements or shared via messaging platforms, bypassing the need for complex backend state management.

Polling for Completion

Once the user is redirected to the provider, the SDK handles completion through the ConnectionRequest class in ts/packages/core/src/models/ConnectionRequest.ts. The waitForConnection() method implements a polling loop that repeatedly calls GET /connected-accounts/{id} until the status transitions to ACTIVE or a terminal error state occurs.

// ts/packages/core/src/models/ConnectionRequest.ts#L30-L55
async waitForConnection(timeoutMs: number = 60000): Promise<ConnectedAccount> {
  const startTime = Date.now();
  const pollInterval = 2000; // 2 seconds
  
  while (Date.now() - startTime < timeoutMs) {
    const status = await this.checkStatus(); // GET /connected-accounts/{this.id}
    
    if (status === 'ACTIVE') {
      return this.getConnectedAccount();
    }
    
    if (['ERROR', 'REVOKED'].includes(status)) {
      throw new ComposioConnectionError(`Connection failed with status: ${status}`);
    }
    
    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }
  
  throw new ComposioTimeoutError('Connection polling timed out');
}

Code Implementation Examples

Full OAuth2 Flow Implementation

This example demonstrates creating an auth config, initiating the flow, and waiting for completion:

import { Composio, AuthSchemeTypes } from '@composio/core';

const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });

async function connectGitHubUser(userId: string) {
  // 1. Create OAuth2 auth config (typically done once per integration)
  const authConfig = await composio.authConfigs.create({
    type: 'use_custom_auth',
    name: 'GitHub OAuth',
    authScheme: AuthSchemeTypes.OAUTH2,
    credentials: {
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      scopes: ['repo', 'read:user'],
    },
  });

  // 2. Initiate connection request
  const request = await composio.connectedAccounts.initiate(
    userId,
    authConfig.id,
    {
      callbackUrl: 'https://myapp.com/composio/callback',
    }
  );

  // 3. Redirect user to provider consent page
  console.log('Authorization URL:', request.redirectUrl);
  
  // 4. Poll until user completes authorization
  const connectedAccount = await request.waitForConnection(120000);
  
  console.log('Connected Account ID:', connectedAccount.id);
  console.log('Toolkit:', connectedAccount.toolkit.slug);
  
  return connectedAccount;
}

For scenarios requiring minimal backend logic, use the link() method:

async function generateConnectLink(userId: string, authConfigId: string) {
  const linkRequest = await composio.connectedAccounts.link(
    userId,
    authConfigId,
    {
      callbackUrl: 'https://myapp.com/composio/callback',
      // Optional: customize redirect after completion
      redirectUrl: 'https://myapp.com/dashboard',
    }
  );

  // Returns a pre-authenticated URL suitable for emails or UI buttons
  return {
    connectUrl: linkRequest.redirectUrl,
    requestId: linkRequest.id,
  };
}

Key Source Files and Architecture

Understanding the codebase structure helps when debugging or extending Composio's authentication capabilities:

File Responsibility
ts/packages/core/src/types/authConfigs.types.ts Defines AuthSchemeTypes enum and auth config payload shapes
ts/packages/core/src/models/ConnectedAccounts.ts Core class implementing initiate() and link() methods for creating connection requests
ts/packages/core/src/models/ConnectionRequest.ts Implements polling logic via waitForConnection() and status checking
ts/packages/core/src/utils/transformers/connectedAccounts.ts Transforms raw API responses into SDK-friendly ConnectedAccount objects
ts/packages/core/src/errors/ Contains specific error types like ComposioFailedToCreateConnectedAccountLink and ComposioConnectedAccountNotFoundError

Summary

  • Composio unifies third-party authentication through a connected account model linked to auth configs that define schemes like OAuth 2, OAuth 1, API keys, and basic auth.
  • The SDK orchestrates the full OAuth lifecycle: creating connection requests via ConnectedAccounts.initiate() or ConnectedAccounts.link(), redirecting users to provider consent pages, and polling for completion through ConnectionRequest.waitForConnection().
  • Type safety is enforced through the AuthSchemeTypes enum in authConfigs.types.ts, ensuring consistent handling across all supported providers.
  • The polling mechanism in ConnectionRequest.ts automatically handles the transition from PENDING to ACTIVE status, returning fully initialized account objects with valid tokens.

Frequently Asked Questions

What authentication schemes does Composio support?

Composio supports OAuth 1, OAuth 2, API Key, Basic authentication, Bearer Token, and Google Service Account authentication. These schemes are defined in the AuthSchemeTypes enum located in ts/packages/core/src/types/authConfigs.types.ts, allowing the SDK to handle any third-party provider using the same consistent interface regardless of the underlying protocol.

How does the SDK know when a user has completed OAuth authorization?

The SDK implements a polling mechanism through the waitForConnection() method in the ConnectionRequest class. After initiating a connection, the SDK repeatedly calls GET /connected-accounts/{id} every 2 seconds until the status changes to ACTIVE, indicating successful authorization. If the status reaches an error state or the timeout expires (default 60 seconds), the method throws a specific error from the ts/packages/core/src/errors/ directory.

The initiate() method creates a full-stack connection request via POST /connected-accounts, suitable for backend-driven OAuth flows where you manage the state and callback handling. The link() method creates a lightweight "Connect-Link" via POST /link/create, generating a pre-authenticated URL ideal for sharing in emails, embedding in UI buttons, or scenarios requiring minimal backend logic. Both return a ConnectionRequest object supporting waitForConnection().

Where are OAuth credentials stored in the Composio architecture?

OAuth credentials are stored within auth configs created through the SDK or API. When you call composio.authConfigs.create(), you provide credentials like client_id and client_secret which are persisted on Composio's backend and associated with your integration. During the OAuth flow, the backend uses these stored credentials to negotiate with the provider, while the SDK only handles the orchestration and polling via the ConnectedAccounts and ConnectionRequest models.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →