Custom Auth Configs

Customize auth configs for any toolkit

Many toolkits support a level of customization for the auth config, specifically OAuth applications.

This guide will walk you through the process of customizing the auth config for toolkits where you can configure your own developer app.

Creating a custom auth config

To create a custom auth config, click Create Auth Config in your dashboard, then navigate to Authentication managementManage authentication with custom credentials.

You'll need to customize the auth config when you want to use different values than the defaults - such as your own subdomain, base URL, client ID, client secret, etc.

You may change the subdomain for the PostHog toolkit to match your own instance.

PostHog Auth Config Settings
PostHog Auth Config Settings

For Hubspot you may customize everything here. For each auth scheme there is a different set of fields.

If you choose to use your own developer app for the OAuth2 scheme, you will have to provide the client ID and client secret.

Hubspot Auth Config Settings
Hubspot Auth Config Settings

Toolkits that support OAuth2 allow using your own developer app. This is the recommended approach for most cases.

Use your own developer app!

We recommend using your own developer app for the OAuth2 scheme as it is more suited for production usage with many users and more granular control over scopes.

However, getting OAuth approvals takes time, so Composio provides a default developer app!

OAuth2 Auth Configs

Generate the OAuth Client ID and Client Secret

To set up a custom OAuth config, you'll need the OAuth Client ID and Client Secret.

You can generate the client ID and client secret from your provider's OAuth configuration page.

Examples for Google and GitHub:

Google OAuth Configuration
Google OAuth Configuration
GitHub OAuth Configuration
GitHub OAuth Configuration

Set the Authorized Redirect URI

When creating your OAuth app, make sure to configure the Authorized Redirect URI to point to the Composio callback URL below:

https://backend.composio.dev/api/v3/toolkits/auth/callback

Create the auth config

Once you have the OAuth credentials, you can add them to the auth config in the dashboard.

  1. Select the OAuth2 scheme.
  2. Select the scopes to request from users. Default scopes are pre-filled for most apps.
  3. Add the OAuth client ID and client secret for your developer app. Keep the redirect URL as is for now!
  4. Click Create!
Auth Config Settings
Auth Config Settings

This auth config is now ready to be used in your application!

# Create a new connected account
connection_request = composio.connected_accounts.initiate(
    user_id="user_id",
    auth_config_id="ac_1234",
)
print(connection_request)

# Wait for the connection to be established
connected_account = connection_request.wait_for_connection()
print(connected_account)
const const connReq: ConnectionRequestconnReq = await const composio: Composio<OpenAIProvider>composio.Composio<OpenAIProvider>.connectedAccounts: ConnectedAccounts
Manage authenticated connections
connectedAccounts
.ConnectedAccounts.initiate(userId: string, authConfigId: string, options?: CreateConnectedAccountOptions): Promise<ConnectionRequest>
Compound function to create a new connected account. This function creates a new connected account and returns a connection request. Users can then wait for the connection to be established using the `waitForConnection` method.
@paramuserId - User ID of the connected account@paramauthConfigId - Auth config ID of the connected account@paramoptions - Options for creating a new connected account@returnsConnection request object@example```typescript // For OAuth2 authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { callbackUrl: 'https://your-app.com/callback', config: AuthScheme.OAuth2({ access_token: 'your_access_token', token_type: 'Bearer' }) } ); // For API Key authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { config: AuthScheme.ApiKey({ api_key: 'your_api_key' }) } ); // For Basic authentication const connectionRequest = await composio.connectedAccounts.initiate( 'user_123', 'auth_config_123', { config: AuthScheme.Basic({ username: 'your_username', password: 'your_password' }) } ); ```@linkhttps://docs.composio.dev/reference/connected-accounts/create-connected-account
initiate
(const userId: "user_123"userId, "ac_1234");
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const connReq: ConnectionRequestconnReq.ConnectionRequestState.redirectUrl?: string | null | undefinedredirectUrl);
const
const connection: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}
connection
= await const composio: Composio<OpenAIProvider>composio.Composio<OpenAIProvider>.connectedAccounts: ConnectedAccounts
Manage authenticated connections
connectedAccounts
.ConnectedAccounts.waitForConnection(connectedAccountId: string, timeout?: number): Promise<ConnectedAccountRetrieveResponse>
Waits for a connection request to complete and become active. This method continuously polls the Composio API to check the status of a connection until it either becomes active, enters a terminal error state, or times out.
@paramconnectedAccountId - The ID of the connected account to wait for@paramtimeout - Maximum time to wait in milliseconds (default: 60 seconds)@returnsThe finalized connected account data@throws{ComposioConnectedAccountNotFoundError} If the connected account cannot be found@throws{ConnectionRequestFailedError} If the connection enters a failed, expired, or deleted state@throws{ConnectionRequestTimeoutError} If the connection does not complete within the timeout period@example```typescript // Wait for a connection to complete with default timeout const connectedAccount = await composio.connectedAccounts.waitForConnection('conn_123abc'); // Wait with a custom timeout of 2 minutes const connectedAccount = await composio.connectedAccounts.waitForConnection('conn_123abc', 120000); ```
waitForConnection
(
const connReq: ConnectionRequestconnReq.ConnectionRequestState.id: stringid ); var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(
const connection: {
    id: string;
    authConfig: TypeOf<ZodObject<{
        id: ZodString;
        isComposioManaged: ZodBoolean;
        isDisabled: ZodBoolean;
    }, "strip", ZodTypeAny, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }, {
        id: string;
        isComposioManaged: boolean;
        isDisabled: boolean;
    }>>;
    data?: Record<string, unknown>;
    params?: Record<string, unknown>;
    status: ConnectedAccountStatusEnum;
    ... 6 more ...;
    updatedAt: string;
}
connection
);

By default the users will see an OAuth screen like the one below:

Composio's Domain in OAuth Consent Screen
Composio's Domain in OAuth Consent Screen

The OAuth redirect URL is surfaced in some OAuth providers' consent screens. This may cause confusion for some users as that URL is not of the same domain as the application.

To remediate this:

Set the Authorized Redirect URI

Specify the Authorized Redirect URI to your own domain in the OAuth configuration. For example:

https://yourdomain.com/api/composio-redirect

Create a redirect logic

Create a redirect logic, either through your DNS or in your application to redirect that endpoint to https://backend.composio.dev/api/v3/toolkits/auth/callback

Example: API Route for OAuth Redirect

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

from composio import Composio

# Create a FastAPI app
app = FastAPI()

# Create a Composio client
composio = Composio()


@app.get("/authorize/{toolkit}")
def authorize_app(toolkit: str):
    # retrieve the user id from your app
    user_id = "<user_id>"

    # retrieve the auth config id from your app
    auth_config_id = "<auth_config_id>"

    # initiate the connection request
    connection_request = composio.connected_accounts.initiate(
        user_id=user_id,
        auth_config_id=auth_config_id,
    )
    return RedirectResponse(url=connection_request.redirect_url)
import type { NextApiRequest, 
type NextApiResponse<Data = any> = ServerResponse<IncomingMessage> & {
    send: Send<Data>;
    json: Send<Data>;
    status: (statusCode: number) => NextApiResponse<Data>;
    redirect(url: string): NextApiResponse<Data>;
    redirect(status: number, url: string): NextApiResponse<Data>;
    setDraftMode: (options: {
        enable: boolean;
    }) => NextApiResponse<Data>;
    setPreviewData: (data: object | string, options?: {
        maxAge?: number;
        path?: string;
    }) => NextApiResponse<Data>;
    clearPreviewData: (options?: {
        path?: string;
    }) => NextApiResponse<Data>;
    revalidate: (urlPath: string, opts?: {
        unstable_onlyGenerated?: boolean;
    }) => Promise<void>;
}
Next `API` route response
NextApiResponse
} from 'next';
export default function function handler(req: NextApiRequest, res: NextApiResponse): voidhandler(req: NextApiRequestreq: NextApiRequest, res: NextApiResponseres:
type NextApiResponse<Data = any> = ServerResponse<IncomingMessage> & {
    send: Send<Data>;
    json: Send<Data>;
    status: (statusCode: number) => NextApiResponse<Data>;
    redirect(url: string): NextApiResponse<Data>;
    redirect(status: number, url: string): NextApiResponse<Data>;
    setDraftMode: (options: {
        enable: boolean;
    }) => NextApiResponse<Data>;
    setPreviewData: (data: object | string, options?: {
        maxAge?: number;
        path?: string;
    }) => NextApiResponse<Data>;
    clearPreviewData: (options?: {
        path?: string;
    }) => NextApiResponse<Data>;
    revalidate: (urlPath: string, opts?: {
        unstable_onlyGenerated?: boolean;
    }) => Promise<void>;
}
Next `API` route response
NextApiResponse
) {
// The target Composio endpoint that handles OAuth callbacks const const composioEndpoint: "https://backend.composio.dev/api/v3/toolkits/auth/callback"composioEndpoint = 'https://backend.composio.dev/api/v3/toolkits/auth/callback'; // Extract and preserve all query parameters const const queryParams: URLSearchParamsqueryParams = new var URLSearchParams: new (init?: string[][] | Record<string, string> | string | URLSearchParams) => URLSearchParams
The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL. [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams)
URLSearchParams
();
var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object
.
ObjectConstructor.entries<string | string[] | undefined>(o: {
    [s: string]: string | string[] | undefined;
} | ArrayLike<string | string[] | undefined>): [string, string | string[] | undefined][] (+1 overload)
Returns an array of key/values of the enumerable own properties of an object
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
entries
(req: NextApiRequestreq.
NextApiRequest.query: Partial<{
    [key: string]: string | string[];
}>
Object of `query` values from url
query
).Array<[string, string | string[] | undefined]>.forEach(callbackfn: (value: [string, string | string[] | undefined], index: number, array: [string, string | string[] | undefined][]) => void, thisArg?: any): void
Performs the specified action for each element in an array.
@paramcallbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
forEach
(([key: stringkey, value: string | string[] | undefinedvalue]) => {
if (typeof value: string | string[] | undefinedvalue === 'string') { const queryParams: URLSearchParamsqueryParams.URLSearchParams.append(name: string, value: string): void
The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter. [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)
append
(key: stringkey, value: stringvalue);
} }); // Redirect to Composio with all query parameters intact const const redirectUrl: stringredirectUrl = `${const composioEndpoint: "https://backend.composio.dev/api/v3/toolkits/auth/callback"composioEndpoint}?${const queryParams: URLSearchParamsqueryParams.URLSearchParams.toString(): stringtoString()}`; res: NextApiResponseres.function redirect(status: number, url: string): NextApiResponse<any> (+1 overload)redirect(302, const redirectUrl: stringredirectUrl); }

Create the auth config

Specify your custom redirect URI in the auth config settings!

Auth Config Settings
Auth Config Settings

With this setup, you can use https://yourdomain.com/api/composio-redirect as your OAuth redirect URI, which will create a better user experience by keeping users on your domain during the OAuth flow.