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:
- Your product initializes authentication by sending user to the
/connectendpoint of Membrane engine. - User is redirected to
authorizeUribuilt usinggetOAuthConfigfunction. - User authenticates at the external app and grants permissions.
- OAuth provider redirects back to redirectUri generated by
getOAuthConfigwith authorization code. - Authorization code is exchanged for access and refresh tokens using
getTokenDatafunction. - (optional) Additional credentials are extracted using
getCredentialsFromAccessTokenResponsefunction. - Token response as well as additional extracted credentials are stored as connection credentials.
- Connection is tested using
testfunction 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
makeApiClientfunction. - When credentials expire,
refreshCredentialsfunction is called to refresh them.- If you need to extract additional credentials when refreshing them, it is done with
getCredentialsFromRefreshTokenResponsefunction.
- If you need to extract additional credentials when refreshing them, it is done with
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 configurationconnectionInput- Connection UI parametersredirectUri- The callback URI to usestate- 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
- writeConfiguration 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 configredirect_uri- defaults to{MEMBRANE_API_URI}/oauth-callbackresponse_type=codeaccess_type=offline- to request refresh tokens (setaccess_typetonullinextraparameters to remove it)scope- your scopes joined with spacesstate- as identifier of the current authentication flow- PKCE parameters (unless
skipPkce: true):code_challengecode_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 configurationconnectionInput- Connection UI parameterscodeVerifier- PKCE code verifierqueryParameters- Query params from OAuth callbackredirectUri- The redirect URI used
Default Implementation
If not implemented, makes a POST request to tokenUri with:
Body:
grant_type=authorization_codecode- authorization code from callbackredirect_uri- same URI used in authorize step- Client credentials (based on
clientAuthLocation):headers(default): Basic Authorization header with base64-encodedclientId:clientSecretbody:client_idandclient_secretin request body
- PKCE parameters (unless
skipPkce: true):code_verifiercode_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 configurationconnectionInput- Connection UI parametersqueryParameters- Query params from OAuth callbacktokenResponse- 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 configurationconnectionInput- Connection UI parameterscredentials- Current connection credentialstokenResponse- 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/jsonSee makeApiClient for more details.
refreshCredentials
Refreshes expired access tokens using the refresh token.
Supported implementation types
Arguments
connectorParameters- Connector configurationconnectionInput- Connection UI parameterscredentials- Current connection credentials (includingrefresh_token)
Default Implementation
If not implemented, makes a POST request to tokenUri with:
Body (x-www-form-urlencoded):
grant_type=refresh_tokenrefresh_token- from connection credentialsclient_id- from configclient_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.
Updated 2 days ago
