> For the complete documentation index, see [llms.txt](https://developers.make.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.make.com/custom-apps-documentation/app-components/connections/oauth2.md).

# OAuth 2.0

Connection is a link between Make and a third-party service or app. The OAuth 2.0 connection handles the token exchange automatically.

Before you start configuring your OAuth 2.0 connection, create an app on a third-party service.

When creating an app, use:

* `https://www.make.com/oauth/cb/app` as a callback URL together with the `oauth.makeRedirectUri` variable, or:
* `https://www.make.com/oauth/cb/app` as a callback URL together with the `oauth.localRedirectUri` variable, if you are going to request approval of your app, or:
* `https://www.integromat.com/oauth/cb/app` as an old callback URL together with the `oauth.redirectUri` variable. This option is not suggested for new apps.

## OAuth 2.0 authentication process

OAuth 2.0 authentication processes consist of multiple steps that need to be defined in the Connection communication. The communication should be a collection with the keys below. You can use the keys the particular flow requires and disregard those that are unnecessary.

<table><thead><tr><th width="172" valign="top">Key</th><th width="201.33333333333331" valign="top">Type</th><th valign="top">Description</th></tr></thead><tbody><tr><td valign="top"><code>preauthorize</code></td><td valign="top">Request specification</td><td valign="top">Describes a request that should be executed prior to the <code>authorize</code> directive.</td></tr><tr><td valign="top"><code>authorize</code></td><td valign="top">Request specification</td><td valign="top">Describes the authorization process.</td></tr><tr><td valign="top"><code>token</code></td><td valign="top">Request specification</td><td valign="top">Describes a request that exchanges credentials for tokens.</td></tr><tr><td valign="top"><code>info</code></td><td valign="top">Request specification</td><td valign="top">Describes a request that validates a connection. The most common way to validate the connection is to call an API’s method to get a user’s information. Most of the APIs have such a method. The <code>info</code> directive can be used to store account's metadata.</td></tr><tr><td valign="top"><code>refresh</code></td><td valign="top">Request specification</td><td valign="top">Describes a request that refreshes an access token.</td></tr><tr><td valign="top"><code>invalidate</code></td><td valign="top">Request specification</td><td valign="top">Describes a request that invalidates acquired access token.</td></tr></tbody></table>

Each section is responsible for executing its part in the OAuth 2.0 flow.

You can describe the initial OAuth 2.0 flow as follows:

```
preauthorize => authorize => token => info
```

with `preauthorize` and `info` sections being optional, and `refresh` and `invalidate` not being a part of the initial OAuth 2.0 flow.

{% hint style="info" %}
If the `authorize` directive isn't used (for example, in the client credentials flow), Make doesn't redirect the user and runs the `token` request only if the `token` block contains a `condition`. Use simple `"condition": true` if no special condition is required. Without it, the connection never gets an access token from the `token` request. See [token.condition](#token.condition).
{% endhint %}

## Components

### Communication

For more information, see the [communication](/custom-apps-documentation/component-blocks/api.md) documentation.

* `aws` directive is not available
* `pagination` directive is not available
* `response.limit` is not available
* `response.iterate` directive is not available
* `response.output` is not available
* `response` is extended with `data`
* `response` is extended with `expires`

#### response.data

The `data` directive saves data to the connection so that it can be later accessed from a module through the `connection` variable. It functions similarly to the `temp` directive, except that `data` is persisted to the connection.

{% tabs %}
{% tab title="Code example" %}

```json
{
    "response": {
        "data": {
            "accessToken": "{{body.token}}"
        }
    }
}
```

{% endtab %}

{% tab title="Code example used to access later" %}

```json
{
    "url": "http://example.com",
    "qs": {
        "token": "{{connection.accessToken}}"
    }
}
```

{% hint style="info" %}
This `accessToken` can be later accessed in any module that uses this connection or in the app base.
{% endhint %}
{% endtab %}
{% endtabs %}

#### response.expires

The `expires` directive indicates the expiration datetime for tokens.

It's generally used only in the `token` and `refresh` blocks of the connection communication and there are two variations:

* `response.data.expires` is used to trigger the token refresh request.
* `response.expires` is used to prompt the user to manually reauthorize the connection. This is necessary when the access token can no longer be automatically refreshed, such as when a refresh token expires. This is generally defined by the `refresh_expires_in` value in the response from the access token request, or specified in the documentation.

When the date is reached, the connection needs to be reauthorized manually, either from a scenario or the Connections tab.

{% tabs %}
{% tab title="Connection" %}

```json
"token": {
    ...
    "response": {
        "data": {
          "expires": "{{addSeconds(now, body.expires_in)}}" // the access token expiration datetime
        }
        "expires": "{{addSeconds(now, body.refresh_expires_in)}}" // the refresh token expiration datetime
    }
}
```

{% hint style="info" %}
If `refresh_expires_in` is not included in the reply but the API documentation mentions refresh tokens expire after a certain time, you should use that value. For example, for a year: `"{{addYears(now, 1)}}"`
{% endhint %}
{% endtab %}
{% endtabs %}

#### token.condition

By default, Make runs the `token` request only while the connection is being created or reauthorized: it exchanges the authorization code from the `authorize` step for an access token and stores it in the connection.

If the `token` request can be repeated without the user, add a `condition` to the `token` block. A typical case is the client credentials flow, where the request only needs the client ID and client secret that are already stored in the connection. The condition tells Make when to request a new access token.

Make evaluates the condition against the data stored in the connection (`data`) whenever a module is about to use the connection, and also when the connection is created or verified. When the condition is true, Make repeats the `token` request before the module continues and saves the new access token, together with everything else from `response.data`, to the connection. When the condition is false, the module uses the access token that is already stored.

The usual condition compares the stored expiration of the access token with the current time. The condition `{{data.expires < addMinutes(now, 15)}}` requests a new access token 15 minutes before the stored one expires, so a module never starts with an access token that is about to expire. For this to work, the `token` request has to store the expiration in `response.data.expires` (see [response.expires](#response.expires)). When the connection doesn't have a stored expiration yet, for example right after it is created, the condition is true. The same condition therefore also gets the first access token.

{% hint style="warning" %}
Use `token.condition` only when the `token` request doesn't need the user. Don't use it in the authorization code flow: the authorization code from the `authorize` step can be used only once, so repeating the `token` request fails. In that flow, renew the access token with the `refresh` directive instead.

`token.condition` works only when `token` is a single request, not an array of requests. With `token.condition`, you don't need a `refresh` block: the `token` request itself renews the access token.
{% endhint %}

**Example: Renew the access token before it expires**

```json
"token": {
    "condition": "{{data.expires < addMinutes(now, 15)}}", // request a new access token 15 minutes before the stored one expires
    "url": "https://example.com/token",
    "method": "POST",
    "type": "urlencoded",
    "body": {
        "grant_type": "client_credentials",
        "client_id": "{{parameters.clientId}}",
        "client_secret": "{{parameters.clientSecret}}"
    },
    "response": {
        "data": {
            "accessToken": "{{body.access_token}}",
            "expires": "{{addSeconds(now, body.expires_in)}}" // the access token expiration datetime, read by the condition above
        }
    },
    "log": {
        "sanitize": [
            "request.body.client_secret",
            "response.body.access_token"
        ]
    }
}
```

**Example: Request a new access token every time**

Use this variant only if you don't know when the access token expires. Make then requests a new access token every time a module uses the connection. This always works, but each module run sends one extra request to the API.

```json
"token": {
    "condition": true, // request a new access token every time a module uses the connection
    ...
}
```

If the API documentation states how long an access token is valid, prefer the expiration-based condition from the previous example and store a fixed expiration instead. For example, for an access token that is valid for one hour: `"expires": "{{addMinutes(now, 55)}}"`

### Parameters​ <a href="#parameters" id="parameters"></a>

[Parameters](/custom-apps-documentation/block-elements/parameters.md) the user needs to provide when setting up a new connection.

### Default scope

[Default scope](/custom-apps-documentation/component-blocks/scope.md) for every new connection.

### Scope list

Collection of available [scopes](/custom-apps-documentation/component-blocks/scopes.md).

### ​Common data​ <a href="#common-data" id="common-data"></a>

[Non-user-specific sensitive values](/custom-apps-documentation/app-components/connections.md) like secrets.

## Available IML variables

These IML variables are available for you to use everywhere in this module:

<table><thead><tr><th width="233.4444580078125" valign="top">Variable</th><th valign="top">Description</th></tr></thead><tbody><tr><td valign="top"><code>now</code></td><td valign="top">Current date and time</td></tr><tr><td valign="top"><code>environment</code></td><td valign="top">TBD</td></tr><tr><td valign="top"><code>temp</code></td><td valign="top">Contains custom variables created via <code>temp</code> directive.</td></tr><tr><td valign="top"><code>parameters</code></td><td valign="top">Contains the connection’s input parameters.</td></tr><tr><td valign="top"><code>common</code></td><td valign="top">Contains the connection’s common data collection.</td></tr><tr><td valign="top"><code>data</code></td><td valign="top">Contains the connection’s data collection.</td></tr><tr><td valign="top"><code>oauth.scope</code></td><td valign="top">Contains an array of scope required to be passed to the OAuth 2.0 authorization process.</td></tr><tr><td valign="top"><code>oauth.redirectUri</code></td><td valign="top">Contains the redirect URL for the OAuth 2.0 authorization process in this format: <code>https://www.integromat.com/oauth/cb/app</code></td></tr><tr><td valign="top"><code>oauth.localRedirectUri</code></td><td valign="top">Contains the redirect URL for the OAuth 2.0 authorization process in this format: <code>https://www.make.com/oauth/cb/app</code> or this format:<br><code>https://www.private-instance.com/oauth/cb/app</code></td></tr><tr><td valign="top"><code>oauth.makeRedirectUri</code></td><td valign="top">Contains the redirect URL for the OAuth 2.0 authorization process in this format: <code>https://www.make.com/oauth/cb/app</code></td></tr></tbody></table>

## Code grant example

{% tabs %}
{% tab title="Common data" %}

```json
{
    "clientId": "3j2h5g234iuy75467k3g457kj34bl",
    "clientSecret": "m034f8756y0cw3k8t7hkxl0esybv"
}
```

{% endtab %}

{% tab title="Default scope" %}

```json
[ "user.read", "offline_access" ]
```

{% endtab %}

{% tab title="Parameters" %}

```json
[
    {
        "name": "clientId",
        "type": "text",
        "label": "Client ID",
        "advanced": true,
        "editable": true
    },
    {
        "name": "clientSecret",
        "type": "password",
        "label": "Client secret",
        "advanced": true,
        "editable": true
    },
    {
        "name": "scopes",
        "label": "Additional scopes",
        "type": "array",
        "labels": {
            "add": "Add scope"
        },
        "spec": {
            "type": "text",
            "label": "Scope"
        },
        "help": "Use this to get access to extra scopes.",
        "advanced": true,
        "editable": true
    }
]
```

{% endtab %}

{% tab title="Communication" %}

```json
{
    "authorize": {
        "url": "https://example.com/authorize",
        "qs": {
            "scope": "{{join(distinct(merge(oauth.scope, ifempty(parameters.scopes, emptyarray))), ' ')}}",
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "redirect_uri": "{{oauth.localRedirectUri}}",
            "response_type": "code"
            // You generally don't need to specify the 'state' value, Make handles that for you.
        },

        "response": {
            "temp": {
                "code": "{{query.code}}"
            }
        }
    },
    
    "token": {
        "url": "https://example.com/token",
        "method": "POST",
        "body": {
            "code": "{{temp.code}}",
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "grant_type": "authorization_code",
            "redirect_uri": "{{oauth.localRedirectUri}}",
            "client_secret": "{{ifempty(parameters.clientSecret, common.clientSecret)}}"
        },
        "type": "urlencoded",

        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}",
                "refreshToken": "{{body.refresh_token}}"
            },
            "expires": "{{addSeconds(now, body.refresh_expires_in)}}" // refresh token expiration. The connection needs manual reauthorization once this date is reached.
        },
        
        "log": {
            "sanitize": [
                "request.body.code",
                "request.body.client_secret",
                "response.body.access_token",
                "response.body.refresh_token"
            ]
        }
    },
    
    "refresh": {
        "condition": "{{data.expires < addMinutes(now, 15)}}",
        "url": "https://example.com/token",
        "method": "POST",
        "body": {
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "grant_type": "refresh_token",
            "client_secret": "{{ifempty(parameters.clientSecret, common.clientSecret)}}",
            "refresh_token": "{{data.refreshToken}}"
        },
        "type": "urlencoded",
        
        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}",
                "refreshToken": "{{body.refresh_token}}"
            }
        },
        
        "log": {
            "sanitize": [
                "request.body.client_secret",
                "request.body.refresh_token",
                "response.body.access_token",
                "response.body.refresh_token"
            ]
        }
    },
    
    "info": {
        "url": "https://example.com/me",
        "headers": {
            "authorization": "Bearer {{connection.accessToken}}"
        },
        
        "response": {
            "metadata": { // This is added under the connection name in the UI to make it easier for the user to identify it.
                "type": "text",
                "value": "{{body.data.name}} ({{body.data.email}})"
            },
            "uid": "{{body.data.id}}" // This is only needed if your app uses Shared webhooks. The ID allows Make to match the payloads del;ivered to the shared webhook to the correct recipients.
        },
        
        "log": {
            "sanitize": [ "request.headers.authorization" ]
        }
    }
}
```

{% endtab %}
{% endtabs %}

## OAuth 2.0 code grant example with PCKE

{% tabs %}
{% tab title="Common data" %}

```json
{
    "clientId": "3j2h5g234iuy75467k3g457kj34bl",
    "clientSecret": "m034f8756y0cw3k8t7hkxl0esybv"
}
```

{% endtab %}

{% tab title="Default scope" %}

```json
[ "user.read", "offline_access" ]
```

{% endtab %}

{% tab title="Parameters" %}

```json
[
    {
        "name": "clientId",
        "type": "text",
        "label": "Client ID",
        "advanced": true,
        "editable": true
    },
    {
        "name": "clientSecret",
        "type": "password",
        "label": "Client secret",
        "advanced": true,
        "editable": true
    },
    {
        "name": "scopes",
        "label": "Additional scopes",
        "type": "array",
        "labels": {
            "add": "Add scope"
        },
        "spec": {
            "type": "text",
            "label": "Scope"
        },
        "help": "Use this to get access to extra scopes.",
        "advanced": true,
        "editable": true
    }
]
```

{% endtab %}

{% tab title="Communication" %}

```json
{
    "authorize": {
        "temp": {
            "code_verifier": "{{uuid}}"
        },
        "qs": {
            "scope": "{{join(distinct(merge(oauth.scope, ifempty(parameters.scopes, emptyarray))), ' ')}}",
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "code_challenge": "{{base64url(sha256(temp.code_verifier, 'base64'))}}",
            "code_challenge_method": "S256",
            "redirect_uri": "{{oauth.redirectUri}}",
            "response_type": "code"
        },
        "url": "https://example.com/authorize",

        "response": {
            "temp": {
                "code": "{{query.code}}"
            }
        }
    },

    "token": {
        "url": "https://example.com/token",
        "body": {
            "code": "{{temp.code}}",
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "grant_type": "authorization_code",
            "redirect_uri": "{{oauth.redirectUri}}",
            "code_verifier": "{{temp.code_verifier}}"
        },
        "type": "urlencoded",
        "method": "POST",

        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}",
                "refreshToken": "{{body.refresh_token}}"
            },
            "expires": "{{addSeconds(now, body.refresh_expires_in)}}" // refresh token expiration. The connection needs manual reauthorization once this date is reached.
        },

        "log": {
            "sanitize": [
                "request.body.code",
                "response.body.access_token",
                "response.body.refresh_token"
            ]
        }
    },

    "refresh": {
        "condition": "{{data.expires < addMinutes(now, 15)}}",
        "url": "https://example.com/token",
        "method": "POST",
        "body": {
            "grant_type": "refresh_token",
            "client_id": "{{ifempty(parameters.clientId, common.clientId)}}",
            "refresh_token": "{{data.refreshToken}}"
        },
        "type": "urlencoded",

        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}",
                "refreshToken": "{{body.refresh_token}}"
            }
        },

        "log": {
            "sanitize": [
                "request.body.refresh_token",
                "response.body.access_token",
                "response.body.refresh_token"
            ]
        }
    },
    "info": {
        "url": "https://example.com/v1/info",
        "headers": {
            "authorization": "Bearer {{connection.accessToken}}"
        },

        "response": {
            "error": {
                "message": "[{{statusCode}}] {{body.error}}"
            },
            "metadata": { // This is added under the connection name in the UI to make it easier for the user to identify it.
                "type": "text",
                "value": "{{body.user.fullName}} ({{body.user.email}})"
            },
            "data": {
                "userId": "{{body.user.id}}"
            },
            "uid": "{{body.user.id}}" // This is only needed if your app uses Shared webhooks. The ID allows Make to match the payloads del;ivered to the shared webhook to the correct recipients.
        },

        "log": {
            "sanitize": [
                "request.headers.authorization"
            ]
        }
    }
}
```

{% endtab %}
{% endtabs %}

## Client credentials example

{% tabs %}
{% tab title="Common data" %}
{% hint style="info" %}
In the client credentials flow, the user must provide the Client ID and secret, so no common data is defined.
{% endhint %}
{% endtab %}

{% tab title="Default scope" %}

```json
[ "user.read", "offline_access" ]
```

{% endtab %}

{% tab title="Parameters" %}

```json
[
    {
        "name": "clientId",
        "type": "text",
        "label": "Client ID",
        "required": true,
        "editable": true
    },
    {
        "name": "clientSecret",
        "type": "password",
        "label": "Client secret",
        "required": true,
        "editable": true
    },
    {
        "name": "scopes",
        "label": "Additional scopes",
        "type": "array",
        "labels": {
            "add": "Add scope"
        },
        "spec": {
            "type": "text",
            "label": "Scope"
        },
        "help": "Use this to get access to extra scopes.",
        "advanced": true,
        "editable": true
    }
]
```

{% endtab %}

{% tab title="Communication" %}

```json
{
    "token": {
        /*
        Alternative to the "refresh" block below: with this condition, the "token" request itself gets the first
        access token when the connection is created, and a new one 15 minutes before the stored one expires.
        The "refresh" block is then not needed at all and can be replaced by this condition. See token.condition.

        "condition": "{{data.expires < addMinutes(now, 15)}}",
        */

        "url": "https://example.com/token",
        "body": {
            "grant_type": "client_credentials",
            "client_id": "{{parameters.clientId}}",
            "client_secret": "{{parameters.clientSecret}}",
            "scope": "{{join(distinct(merge(oauth.scope, ifempty(parameters.scopes, emptyarray))), ' ')}}"
        },
        "type": "urlencoded",
        "method": "POST",

        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}"
            }
        },

        "log": {
            "sanitize": [
                "response.body.access_token"
            ]
        }
    },
    "refresh": {
        "condition": "{{data.expires < addMinutes(now, 15)}}",

        "url": "https://example.com/token",
        "body": {
            "grant_type": "client_credentials",
            "client_id": "{{connection.clientId}}",
            "client_secret": "{{connection.clientSecret}}",
            "scope": "{{join(distinct(merge(oauth.scope, ifempty(parameters.scopes, emptyarray))), ' ')}}"
        },
        "type": "urlencoded",
        "method": "POST",

        "response": {
            "data": {
                "expires": "{{addSeconds(now, body.expires_in)}}", // access token expiration. This is used in the condition to trigger the "refresh" request.
                "accessToken": "{{body.access_token}}"
            }
        },

        "log": {
            "sanitize": [
                "response.body.access_token"
            ]
        }
    },
    "info": {
        "url": "https://example.com/v1/info",
        "headers": {
            "authorization": "Bearer {{connection.accessToken}}"
        },

        "response": {
            "error": {
                "message": "[{{statusCode}}] {{body.error}}"
            },
            "metadata": { // This is added under the connection name in the UI to make it easier for the user to identify it.
                "type": "text",
                "value": "{{body.user.fullName}} ({{body.user.email}})"
            },
            "uid": "{{body.user.id}}" // This is only needed if your app uses Shared webhooks. The ID allows Make to match the payloads del;ivered to the shared webhook to the correct recipients.
        },
        
        "log": {
            "sanitize": [
                "request.headers.authorization"
            ]
        }
    }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.make.com/custom-apps-documentation/app-components/connections/oauth2.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
