Manual auth management
Manual authentication lets you connect users to toolkits outside the chat flow. Reach for it when you want to:
- Pre-authenticate users before they start chatting.
- Build a custom connections UI in your app.
Authorize a toolkit
Call session.authorize() to generate a Connect Link URL, redirect the user, and wait for them to finish:
session = composio.create(user_id="user_123")
connection_request = session.authorize("gmail")
print(connection_request.redirect_url)
# https://connect.composio.dev/link/ln_abc123
connected_account = connection_request.wait_for_connection(60000)
print(f"Connected: {connected_account.id}")Redirect the user to the redirectUrl. After they authenticate, they'll return to your callback URL. The connection request polls until the user completes authentication (default timeout: 60 seconds).
If the user closes the Connect Link without completing auth, the connection remains in INITIATED status until it expires.
Reusing an active connection
In TypeScript, call session.ensureConnected("gmail") to reuse the session's active connection. If none exists, it starts an authorization flow and waits for the connection to become active. The optional timeout sets how long to wait in milliseconds (default: 60000).
The result includes toolkit, wasConnected, and connectedAccount. wasConnected is true when an active connection already exists. Toolkits that don't require authentication also return wasConnected: true, with no connectedAccount.
Use session.authorize() and waitForConnection() when you need to display or redirect to the Connect Link. ensureConnected() waits internally and doesn't return the redirect URL. Calling authorize() always starts a new authorization flow, even if an active connection exists.
Redirecting users after authentication
Pass a callbackUrl to control where users land after authenticating. You can include query parameters to carry context through the flow, for example to identify which userID or session triggered the connection.
connection_request = session.authorize(
"gmail",
callback_url="https://your-app.com/callback?user_id=user_123&source=onboarding"
)
print(connection_request.redirect_url)After authentication, Composio redirects the user to your callback URL with the following parameters appended, while preserving your existing ones:
| Parameter | Description |
|---|---|
status | success or failed |
connected_account_id | The ID of the newly created connected account |
https://your-app.com/callback?user_id=user_123&source=onboarding&status=success&connected_account_id=ca_abc123Identify the connected account
After authentication, use the account returned by wait_for_connection() or waitForConnection() to identify which provider account connected. If you use a callback instead, verify its connected_account_id against the connection attempt stored on your server and the signed-in application user before retrieving it. Callback query parameters alone aren't proof of ownership.
These identifiers serve different purposes:
| Value | Meaning |
|---|---|
| Application user ID | Your application's user, such as user_123. This doesn't identify their Gmail or GitHub account. |
| Connected account ID | One connection in Composio, such as ca_abc123. Use it to select the account for a profile request. |
| Alias | A label you assign, such as work-gmail. It doesn't prove which account connected. |
| Provider identity | The email address, username, or ID returned by the provider for the authenticated account. |
For a "Connected as…" label, read state.val.displayName when available. For additional profile fields, get the toolkit's current-user endpoint and call it through Composio's authenticated proxy with the same connected account ID:
# Continue after connected_account = connection_request.wait_for_connection().
account_id = connected_account.id
state = connected_account.model_dump().get("state") or {}
display_name = state.get("val", {}).get("displayName")
print(f"Connected as {display_name}" if display_name else "Account connected")
toolkit = composio.toolkits.get(connected_account.toolkit.slug)
endpoint = toolkit.get_current_user_endpoint
method = toolkit.get_current_user_endpoint_method
if not endpoint or method not in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"):
raise ValueError("This toolkit doesn't publish a supported current-user endpoint")
response = composio.tools.proxy(
endpoint=endpoint,
method=method,
connected_account_id=account_id,
)
if response["status"] != 200:
raise RuntimeError("Could not retrieve the connected account's profile")
profile = response["data"] # Parse the fields documented by this provider.Toolkit metadata describes where to request a profile; it doesn't contain the account owner's details. Endpoint availability, required scopes, and response fields vary by provider. If the metadata is absent, use a documented profile endpoint or tool for that provider. A GitHub username or display label isn't a verified email address.
For an application policy, validate the provider's response before giving an agent access to the account. Follow Restrict Gmail connections to an email domain for a complete example that checks the mailbox address and disables rejected connections. Return only the fields your UI needs, not the account's authentication state.
Check connection status
Use session.toolkits() to see all toolkits in the session and their connection status:
toolkits = session.toolkits()
for toolkit in toolkits.items:
status = toolkit.connection.connected_account.id if toolkit.connection.is_active else "Not connected"
print(f"{toolkit.name}: {status}")Disabling in-chat auth
By default, sessions include the COMPOSIO_MANAGE_CONNECTIONS meta-tool that prompts users to authenticate during chat. To turn it off and handle auth entirely in your own UI, set manage_connections to False:
session = composio.create(
user_id="user_123",
manage_connections=False,
)Putting it together
A common pattern is to verify all required connections before starting the agent:
from composio import Composio
composio = Composio(api_key="your-api-key")
required_toolkits = ["gmail", "github"]
session = composio.create(
user_id="user_123",
manage_connections=False, # Disable in-chat auth prompts
)
toolkits = session.toolkits()
connected = {t.slug for t in toolkits.items if t.connection.is_active}
pending = [slug for slug in required_toolkits if slug not in connected]
print(f"Connected: {connected}")
print(f"Pending: {pending}")
for slug in pending:
connection_request = session.authorize(slug)
print(f"Connect {slug}: {connection_request.redirect_url}")
connection_request.wait_for_connection()
print("All toolkits connected!")Next
Managing multiple connected accounts
Let a user connect work and personal accounts for the same toolkit, then pick which one runs