OAuth2

OAuth2 Authentication

This authentication type implements OAuth 2. It can be configured to work with different variations of the standard.

Connector Definition

Example OAuth2 connector definition:

# 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

Arguments

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

Example Implementation

# 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

ParameterRequiredDescription
clientIdYesOAuth2 client ID
clientSecretYesOAuth2 client secret
authorizeUriYesAuthorization endpoint URL
tokenUriYesToken exchange endpoint URL
scopesNoArray of OAuth scopes to request
clientAuthLocationNoWhere to send credentials: headers (default), body, or both
noRefreshTokenNoSet true if API doesn't return refresh tokens
skipPkceNoSet true to disable PKCE (enabled by default)
extraNoAdditional 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

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

// 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

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)
// 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

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
// 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

Arguments

  • credentials - Connection credentials

Example Implementation

# 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 for more details.

refreshCredentials

Refreshes expired access tokens using the refresh token.

Supported implementation types

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:

// 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 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.