OAuth2

OAuth2 Authentication

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

Specification

Here is how the OAuth2 authentication is defined in your connector's spec.yml:

auth:
  type: oauth2

  # Required functions
  getOAuthConfig:
    implementationType: mapping
  makeApiClient:
    implementationType: mapping
  test:
    implementationType: javascript

  # Optional functions
  getTokenData:
    implementationType: javascript
  getCredentialsFromAccessTokenResponse:
    implementationType: javascript
  getCredentialsFromRefreshTokenResponse:
    implementationType: javascript
  refreshCredentials:
    implementationType: javascript

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
  • connectionParameters - Connection UI parameters
  • redirectUri - The callback URI to use
  • state - Generated state for the OAuth 2 flow

Example Implementation

# File: auth/get-oauth-config.map.yml
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 are appended to the URI.

Example with extra parameters:

extra:
  prompt: consent
  custom_param: value

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
  • connectionParameters - 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

// File: auth/get-token-data.js
export default async function ({
  connectorParameters,
  connectionParameters,
  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
  • connectionParameters - 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)
// File: auth/get-credentials-from-access-token-response.js
export default async function ({
  connectorParameters,
  connectionParameters,
  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
  • connectionParameters - 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
// File: auth/get-credentials-from-refresh-token-response.js
export default async function ({
  connectorParameters,
  connectionParameters,
  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.

test

Tests if the connection is valid by making an API call.

Supported implementation types

Arguments

  • apiClient - The API client created by makeApiClient

Example Implementation

// File: auth/test.js
export default async function ({ apiClient }) {
  // Make a simple API call to verify credentials
  await apiClient.request({
    url: "/user",
    method: "GET",
  })

  // Return true if successful
  return true
}

See test for more details.

refreshCredentials

Refreshes expired access tokens using the refresh token.

Supported implementation types

Arguments

  • connectorParameters - Connector configuration
  • connectionParameters - 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:

// File: auth/refresh-credentials.js
export default async function ({
  connectorParameters,
  connectionParameters,
  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.