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:
- Your product initializes authentication by sending user to the
/connect
endpoint of Membrane engine. - User is redirected to
authorizeUri
built usinggetOAuthConfig
function. - User authenticates at the external app and grants permissions.
- OAuth provider redirects back to redirectUri generated by
getOAuthConfig
with authorization code. - Authorization code is exchanged for access and refresh tokens using
getTokenData
function. - (optional) Additional credentials are extracted using
getCredentialsFromAccessTokenResponse
function. - Token response as well as additional extracted credentials are stored as connection credentials.
- 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.
- 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 configurationconnectionParameters
- Connection UI parametersredirectUri
- The callback URI to usestate
- 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
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-callback
response_type=code
access_type=offline
- to request refresh tokens (setaccess_type
tonull
inextra
parameters to remove it)scope
- your scopes joined with spacesstate
- 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 configurationconnectionParameters
- 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_code
code
- authorization code from callbackredirect_uri
- same URI used in authorize step- Client credentials (based on
clientAuthLocation
):headers
(default): Basic Authorization header with base64-encodedclientId:clientSecret
body
:client_id
andclient_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 configurationconnectionParameters
- 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)
// 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 configurationconnectionParameters
- 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
// 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 configurationconnectionParameters
- 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_token
refresh_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:
// 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.
Updated about 17 hours ago