> ## Documentation Index
> Fetch the complete documentation index at: https://docs.membrane.agency/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth2

# OAuth2 Authentication

This authentication type implements [OAuth 2](https://oauth.net/2/). It can be configured to work with different variations of the standard.

## Connector Definition

Example OAuth2 connector definition:

```yaml theme={null}
# Connector definition: Minimal OAuth2 connector with standard authorization code flow
type: oauth2

# OAuth configuration: client credentials and endpoints
getOAuthConfig:
  type: mapping
  mapping:
    clientId:
      $var: connectorParameters.clientId
    clientSecret:
      $var: connectorParameters.clientSecret
    authorizeUri: https://auth.example.com/oauth2/authorize
    tokenUri: https://auth.example.com/oauth2/token

# API client: how to make authenticated requests
makeApiClient:
  type: mapping
  mapping:
    baseUri: https://api.example.com
    headers:
      Authorization:
        $concat:
          - 'Bearer '
          - $var: credentials.access_token

# Connection test: verify credentials are valid
test:
  type: javascript
  code: |
    export default async function ({ apiClient }) {
      const user = await apiClient.get("/user")
      return user.id !== undefined
    }
```

## Authentication Flow

The standard OAuth2 authorization code flow:

1. Your product initializes authentication by sending user to the `/connect` endpoint of Membrane engine.
2. User is redirected to `authorizeUri` built using `getOAuthConfig` function.
3. User authenticates at the external app and grants permissions.
4. OAuth provider redirects back to redirectUri generated by `getOAuthConfig` with authorization code.
5. Authorization code is exchanged for access and refresh tokens using `getTokenData` function.
6. (optional) Additional credentials are extracted using `getCredentialsFromAccessTokenResponse` function.
7. Token response as well as additional extracted credentials are stored as connection credentials.
8. Connection is tested using `test` function to determine if it was created successfully.

Then, the following things happen:

* API requests to the external app are made with a client created by `makeApiClient` function.
* When credentials expire, `refreshCredentials` function is called to refresh them.
  * If you need to extract additional credentials when refreshing them, it is done with `getCredentialsFromRefreshTokenResponse` function.

See details of each of the mentioned functions below.

## getOAuthConfig

Returns OAuth2 configuration used to build the authorization URL and token exchange.

**Supported implementation types**

* [Mapping](/docs/references/functions/function-types/mapping)
* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `connectorParameters` - Connector configuration
* `connectionInput` - Connection UI parameters
* `redirectUri` - The callback URI to use
* `state` - Generated state for the OAuth 2 flow

### Example Implementation

```yaml theme={null}
# Connector definition: OAuth configuration: client credentials and endpoints
getOAuthConfig:
  type: mapping
  mapping:
    clientId:
      $var: connectorParameters.clientId
    clientSecret:
      $var: connectorParameters.clientSecret
    authorizeUri: https://auth.example.com/oauth2/authorize
    tokenUri: https://auth.example.com/oauth2/token
    scopes:
      - read
      - write
```

### Configuration Parameters

| Parameter            | Required | Description                                                     |
| -------------------- | -------- | --------------------------------------------------------------- |
| `clientId`           | Yes      | OAuth2 client ID                                                |
| `clientSecret`       | Yes      | OAuth2 client secret                                            |
| `authorizeUri`       | Yes      | Authorization endpoint URL                                      |
| `tokenUri`           | Yes      | Token exchange endpoint URL                                     |
| `scopes`             | No       | Array of OAuth scopes to request                                |
| `clientAuthLocation` | No       | Where to send credentials: `headers` (default), `body`, or both |
| `noRefreshToken`     | No       | Set `true` if API doesn't return refresh tokens                 |
| `skipPkce`           | No       | Set `true` to disable PKCE (enabled by default)                 |
| `extra`              | No       | Additional query parameters for the authorize URI               |

### Authorize URI Formation

The authorize URI is built by taking your `authorizeUri` and adding these parameters:

* `client_id` - from your config
* `redirect_uri` - defaults to `{MEMBRANE_API_URI}/oauth-callback`
* `response_type=code`
* `access_type=offline` - to request refresh tokens (set `access_type` to `null` in `extra` parameters to remove it)
* `scope` - your scopes joined with spaces
* `state` - as identifier of the current authentication flow
* PKCE parameters (unless `skipPkce: true`):
  * `code_challenge`
  * `code_challenge_method=S256`
* Parameters from `extra`

**Redirect URI**

The redirect URI defaults to `{BASE_URI}/oauth-callback` where `BASE_URI` is your Membrane instance URL.

To override, set `oAuthCallbackUri` on the integration to use a custom callback URL.

## getTokenData

Exchanges the authorization code for access and refresh tokens.

**Supported implementation types**

* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `connectorParameters` - Connector configuration
* `connectionInput` - Connection UI parameters
* `codeVerifier` - PKCE code verifier
* `queryParameters` - Query params from OAuth callback
* `redirectUri` - The redirect URI used

**Default Implementation**

If not implemented, makes a POST request to `tokenUri` with:

Body:

* `grant_type=authorization_code`
* `code` - authorization code from callback
* `redirect_uri` - same URI used in authorize step
* Client credentials (based on `clientAuthLocation`):
  * `headers` (default): Basic Authorization header with base64-encoded `clientId:clientSecret`
  * `body`: `client_id` and `client_secret` in request body
* PKCE parameters (unless `skipPkce: true`):
  * `code_verifier`
  * `code_challenge_method=S256`

Headers:

* `Content-Type: application/x-www-form-urlencoded`

The response must include `access_token`. If `noRefreshToken: false` (default), `refresh_token` is also required.

**Example Implementation**

```javascript theme={null}
// Connector definition: getTokenData implementation

getTokenData:
  type: javascript
  code: |
    export default async function ({ connectorParameters, connectionInput, codeVerifier, queryParameters, redirectUri }) {
      const response = await fetch(connectorParameters.tokenUri, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: queryParameters.code,
          redirect_uri: redirectUri,
          client_id: connectorParameters.clientId,
          client_secret: connectorParameters.clientSecret,
          code_verifier: codeVerifier,
          // Add custom parameters
          resource: 'https://api.example.com',
        }),
      });
      return await response.json();
    }
```

## getCredentialsFromAccessTokenResponse

Transforms the token response into connection credentials.

**Supported implementation types**

* [Mapping](/docs/references/functions/function-types/mapping)
* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `connectorParameters` - Connector configuration
* `connectionInput` - Connection UI parameters
* `queryParameters` - Query params from OAuth callback
* `tokenResponse` - Raw response from token exchange

**Default Implementation**

If not implemented, the raw token response is stored as credentials.

**Example Implementation**

Use when you need to:

* Extract specific fields from token response
* Make additional API calls to fetch user info
* Transform or normalize credential structure
* Add computed fields (e.g., token expiration timestamp)

```javascript theme={null}
// Connector definition: getCredentialsFromAccessTokenResponse implementation

getCredentialsFromAccessTokenResponse:
  type: javascript
  code: |
    export default async function ({
      connectorParameters,
      connectionInput,
      queryParameters,
      tokenResponse,
    }) {
      // Make additional API calls if needed
      const userInfoResponse = await fetch('https://api.example.com/userinfo', {
        headers: {
          Authorization: `Bearer ${tokenResponse.access_token}`,
        },
      });

      const userInfo = await userInfoResponse.json();

      // Return transformed credentials
      return {
        access_token: tokenResponse.access_token,
        refresh_token: tokenResponse.refresh_token,
        // Add custom fields
        userId: userInfo.id,
        userName: userInfo.name,
        expires_at: Date.now() + tokenResponse.expires_in * 1000,
      };
    }
```

## getCredentialsFromRefreshTokenResponse

Transforms the refresh token response into updated credentials.

**Supported implementation types**

* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `connectorParameters` - Connector configuration
* `connectionInput` - Connection UI parameters
* `credentials` - Current connection credentials
* `tokenResponse` - Raw response from refresh token request

**Default Implementation**

If not implemented, the raw refresh response is merged with existing credentials.

**Example Implementation**

Use when you need to:

* Transform refresh token response structure
* Calculate token expiration from `expires_in`
* Preserve existing credential fields

```javascript theme={null}
// Connector definition: getCredentialsFromRefreshTokenResponse implementation

getCredentialsFromRefreshTokenResponse:
  type: javascript
  code: |
    export default async function ({
      connectorParameters,
      connectionInput,
      credentials,
      tokenResponse,
    }) {
      return {
        access_token: tokenResponse.access_token,
        // Preserve refresh_token if not returned
        refresh_token: tokenResponse.refresh_token || credentials.refresh_token,
        expires_at: Date.now() + tokenResponse.expires_in * 1000,
      };
    }
```

## makeApiClient

Creates an API client configuration using the connection credentials.

**Supported implementation types**

* [Mapping](/docs/references/functions/function-types/mapping)
* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `credentials` - Connection credentials

**Example Implementation**

```yaml theme={null}
# File: auth/make-api-client.map.yml
args:
  baseUri: https://api.example.com
  headers:
    Authorization:
      $concat:
        values:
          - Bearer
          - $var: credentials.access_token
        delimiter: ' '
    Accept: application/json
```

See [makeApiClient](/reference/workspace-elements/connectors/connector-functions/make-api-client) for more details.

## refreshCredentials

Refreshes expired access tokens using the refresh token.

**Supported implementation types**

* [Javascript](/docs/references/functions/function-types/javascript)

**Arguments**

* `connectorParameters` - Connector configuration
* `connectionInput` - Connection UI parameters
* `credentials` - Current connection credentials (including `refresh_token`)

**Default Implementation**

If not implemented, makes a POST request to `tokenUri` with:

Body (x-www-form-urlencoded):

* `grant_type=refresh_token`
* `refresh_token` - from connection credentials
* `client_id` - from config
* `client_secret` - from config

Headers:

* `Content-Type: application/x-www-form-urlencoded`

Returns updated credentials that are merged with existing credentials.

**Example Implementation**

Implement when the API uses non-standard refresh token flow:

```javascript theme={null}
// Connector definition: refreshCredentials implementation

refreshCredentials:
  type: javascript
  code: |
    export default async function ({
      connectorParameters,
      connectionInput,
      credentials,
    }) {
      const response = await fetch(connectorParameters.tokenUri, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          Authorization: `Basic ${btoa(
            `${connectorParameters.clientId}:${connectorParameters.clientSecret}`
          )}`,
        },
        body: new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: credentials.refresh_token,
          // Add custom parameters
          scope: 'read write',
        }),
      });

      const refreshResponse = await response.json();

      // Return updated credentials (merged with existing)
      return {
        access_token: refreshResponse.access_token,
        refresh_token: refreshResponse.refresh_token || credentials.refresh_token,
        expires_at: Date.now() + refreshResponse.expires_in * 1000,
      };
    }
```

See [refreshCredentials](/reference/workspace-elements/connectors/connector-functions/refresh-credentials) for more details.

## Common Pitfalls

### Missing refresh token configuration

Some APIs don't return refresh tokens (they use long-lived access tokens instead). Check the API's token endpoint documentation for the response format. If refresh token is not explicitly included in the response, set `noRefreshToken: true` in `getOAuthConfig`, otherwise connection creation will fail with a missing refresh token error.
