Implement an OAuth 2.0 authorization code flow with token storage in Jitterbit Studio
Introduction
The OAuth 2.0 authorization code flow lets a Studio operation access a third-party API on behalf of a specific user, using that user's delegated credentials rather than a shared service account. This guide covers the full flow: constructing the authorization URL, receiving the authorization code at a callback endpoint, exchanging the code for access and refresh tokens, storing the refresh token in Cloud Datastore, and refreshing the access token on subsequent runs without user interaction.
The examples in this guide use Google as the authorization provider. The same pattern applies to any OAuth 2.0 provider that supports the authorization code grant type: replace the provider-specific URLs, scopes, and parameter names with the values for your target service.
Use this pattern when:
- The target API requires user-level permissions that a service account cannot provide.
- You need long-lived access using refresh tokens without repeated manual authorization.
- You are building an AI agent or automated workflow that accesses user mailboxes, calendars, or documents.
For APIs that use API keys or client credentials instead of user authorization, see Manage endpoint credentials and Call a REST API using the HTTP v2 connector.
This guide assumes:
- A custom API is configured and published in API Manager in the same environment as the project.
- A Cloud Datastore key storage exists or will be created to hold per-user token records. See Store and retrieve session state using Cloud Datastore for setup steps.
- The project has a way to deliver a URL to the user, such as a Slack message or an App Builder interface.
Design pattern
Three operations implement the OAuth 2.0 authorization code flow:
Build auth URL → Deliver to user"] --> B["User browser
Approve access at provider"] B -->|"Redirect with ?code="| C["Callback operation
Extract code → Exchange for tokens → Store refresh token → Serve confirmation page"] D["Check Auth operation
Read refresh token → Exchange for access token"] --> E["API calls using access token"]
The Connect operation constructs the authorization URL and delivers it to the user. The OAuth Callback operation receives the authorization code when the provider redirects the user's browser, exchanges the code for tokens, and stores the refresh token for future use. The Check Auth operation runs before any API call: it reads the stored refresh token from Cloud Datastore and exchanges it for a fresh access token.
Part 1: Register the OAuth application and store credentials
Before writing any scripts, register Studio as an OAuth application with the authorization provider and store the resulting credentials as project variables.
Register the application
In your OAuth provider's developer console (for example, the Google Cloud Console), create an OAuth 2.0 credential of type Web application and configure:
- Authorized redirect URIs: Add the full URL of the callback endpoint you will create in Part 2. For example:
https://<your-agent-host>/<environment>/<version>/<service-root>/oauth/callback. You must know this URL before completing registration.
The provider will issue a Client ID and Client Secret.
Store credentials as project variables
In Studio, open the project actions menu and select Project Variables. Create the following project variables with their values hidden:
client_id: The OAuth application client ID issued by the provider.client_secret: The OAuth application client secret.redirect_uri: The full callback URL configured in the provider console.
Reference these variables in scripts using the $ prefix (for example, $client_id, $client_secret, $redirect_uri).
Tip
Storing redirect_uri as a project variable makes it easy to update when moving between environments without editing scripts.
Part 2: Create the callback endpoint
The callback endpoint receives the authorization code from the provider after the user approves access. Create it before writing any scripts so the full URL is available to configure in the provider console.
Create the callback operation
-
In Studio, create a new operation. Name it
OAuth Callbackor a similar name. -
Add a Script step as the first step. Leave the script body empty for now. You will add the callback logic in Part 4.
Publish the callback operation as an API endpoint
Follow Expose a Studio operation as a REST API to publish the OAuth Callback operation. When configuring the endpoint, set:
- Method: GET. OAuth providers redirect the user's browser to the callback URL using a GET request with query parameters.
- Path:
/oauth/callback(or any path that matches the redirect URI you will register with the provider). - Response Type: Variable. The callback script sets
$jitterbit.api.response.bodyand$jitterbit.api.response.headers.Content_Typeto serve an HTML confirmation page.
Copy the published endpoint URL. Use it as the redirect_uri project variable value and register it with the provider as an authorized redirect URI.
Part 3: Build the authorization URL
The Connect operation constructs the authorization URL and delivers it to the user.
Create the connect operation
Create a new operation. Name it Connect or a similar name. Add a script step with the following script:
<trans>
$authUrl = "https://accounts.google.com/o/oauth2/v2/auth"
+ "?client_id=" + $client_id
+ "&redirect_uri=" + URLEncode($redirect_uri)
+ "&response_type=code"
+ "&scope=" + URLEncode("https://mail.google.com/ openid email")
+ "&access_type=offline"
+ "&prompt=consent";
</trans>
The key parameters:
response_type=code: Requests the authorization code grant type.scope: The permissions the application requests. Separate multiple scopes with spaces and URL-encode the combined string usingURLEncode. Adjust the scope values to match what your target API requires.access_type=offline: Instructs the provider to issue a refresh token in addition to the access token.prompt=consent: Forces the consent screen to appear even if the user has previously authorized the application. This ensures a new refresh token is issued each time.
Replace the Google authorization endpoint URL and scope values with those for your target provider.
Deliver the URL to the user
After constructing the URL, deliver it so the user can open it in a browser. To send the URL as a Slack ephemeral message (for example, in an agent that uses Slack as its interface):
<trans>
$jitterbit.api.response = "{\"response_type\": \"ephemeral\", \"text\": \"To connect your account, open this link: " + $authUrl + "\"}";
</trans>
Alternatively, return the URL as a plain API response or include it in an email body.
Part 4: Handle the callback and exchange the authorization code
The OAuth Callback operation runs when the provider redirects the user's browser to the registered callback URL. It must extract the authorization code, serve an HTML confirmation page to the browser, exchange the code for tokens, and store the refresh token.
Step 1: Extract the authorization code and serve the confirmation page
In the OAuth Callback operation, open the script step and add:
<trans>
$authCode = $jitterbit.api.request.parameters.code;
$jitterbit.api.response.body = "<html><body><h2>Connected successfully!</h2><p>You can close this window and return to the application.</p></body></html>";
$jitterbit.api.response.headers.Content_Type = "text/html";
$jitterbit.api.response.status_code = "200";
RunOperation("<TAG>Operations/Exchange Token</TAG>");
</trans>
$jitterbit.api.request.parameters.code holds the authorization code from the redirect URL query string. The response body, content type, and status code set here are returned to the browser when the operation completes. RunOperation calls the Exchange Token operation synchronously: the token exchange happens before the response is returned, and the authCode global variable is available to the called operation.
Step 2: Configure the token endpoint connection
Create an HTTP v2 endpoint connected to the provider's token endpoint:
-
In the Project endpoints and connectors tab of the design component palette, click HTTP v2 to open a new connection.
-
Connection Name: Enter a name (for example,
Google OAuth). -
Base URL: Enter the provider's token endpoint base URL (for example,
https://oauth2.googleapis.com). -
Authorization: Select No Auth. The client credentials are included in the request body, not in an authorization header.
-
Click Test, then Save Changes.
Step 3: Build the Exchange Token operation
Create a new operation named Exchange Token. This operation posts the authorization code to the token endpoint and extracts the returned tokens.
Script step (before the POST activity)
Build the form-encoded request body:
<trans>
$tokenRequestBody = "code=" + URLEncode($authCode)
+ "&client_id=" + URLEncode($client_id)
+ "&client_secret=" + URLEncode($client_secret)
+ "&redirect_uri=" + URLEncode($redirect_uri)
+ "&grant_type=authorization_code";
</trans>
HTTP v2 POST activity
-
Drag a POST activity from the Google OAuth endpoint onto the operation canvas.
-
Double-click the activity to open its configuration.
-
Name: Enter
Exchange Token POSTor similar. -
Path: Enter
/token. -
Request Headers: Add
Content-Type/application/x-www-form-urlencoded. -
On the schema step, provide a request schema with a single text field (for example,
body) to hold the URL-encoded string. In the upstream transformation, maptokenRequestBodyto this field. -
Click Finished.
Script step (after the POST activity)
Parse the JSON response and extract the tokens:
<trans>
$refresh_token = TrimChars(GetJSONString($jitterbit.response, "/refresh_token"), "\"");
$access_token = TrimChars(GetJSONString($jitterbit.response, "/access_token"), "\"");
RunOperation("<TAG>Operations/Store Refresh Token</TAG>");
</trans>
$jitterbit.response holds the raw response body from the POST activity. GetJSONString extracts individual fields by JSON path. TrimChars removes the surrounding quotation marks that GetJSONString includes in its output.
Step 4: Store the refresh token
Create a new operation named Store Refresh Token. Use Cloud Datastore Insert Items and Update Items activities with the query-then-branch pattern to persist the refresh token keyed by a unique user identifier (for example, the user's email address or Slack user ID). Map refresh_token to the refresh token field in the Cloud Datastore storage.
See Store and retrieve session state using Cloud Datastore for the full query-insert-update pattern.
Warning
Cloud Datastore stores data in plain text. Do not use it to store the client secret or any other application credential. The refresh token stored here is intentionally user-scoped and should be treated as sensitive. Restrict access to the Cloud Datastore storage to the minimum necessary environments.
Part 5: Refresh the access token on subsequent runs
After the initial authorization, the stored refresh token can be exchanged for a new access token without user interaction. A Check Auth operation handles this and should run at the start of any operation chain that calls the target API.
Create the Check Auth operation
Create a new operation named Check Auth. Add a script step with:
<trans>
RunOperation("<TAG>Operations/Query Token</TAG>");
If(length(trim($refresh_token)) == 0,
RaiseError("No refresh token found. Run the Connect operation to authorize access.")
);
RunOperation("<TAG>Operations/Refresh Access Token</TAG>");
</trans>
The Query Token operation reads the stored refresh token from Cloud Datastore into refresh_token (using the same query-by-key pattern described in Store and retrieve session state using Cloud Datastore). If no token is found, RaiseError stops the chain before any API call is attempted.
Create the Refresh Access Token operation
Create a new operation named Refresh Access Token. Add a script step followed by an HTTP v2 POST activity.
Script step
<trans>
$tokenRequestBody = "refresh_token=" + URLEncode($refresh_token)
+ "&client_id=" + URLEncode($client_id)
+ "&client_secret=" + URLEncode($client_secret)
+ "&grant_type=refresh_token";
</trans>
HTTP v2 POST activity: Use the same Google OAuth connection created in Part 4. Set the path to /token and add the Content-Type: application/x-www-form-urlencoded header. Map tokenRequestBody to the request body in the upstream transformation.
Script step (after the POST activity)
<trans>
$access_token = TrimChars(GetJSONString($jitterbit.response, "/access_token"), "\"");
</trans>
The access_token variable is now available to any subsequent operation in the chain. Pass it as a Bearer token in the Authorization header of outbound API requests:
<trans>
$jitterbit.api.request.headers.Authorization = "Bearer " + $access_token;
</trans>
Note
Most OAuth providers issue access tokens with a short expiry (typically one hour). Call the Check Auth operation before each API call that requires a valid token rather than storing the access token across runs.
Verify the integration
-
Deploy the project.
-
Run the
Connectoperation and open the authorization URL it produces in a browser. -
Follow the OAuth consent flow in the browser. After approving access, the browser should display the HTML confirmation page served by the
OAuth Callbackoperation. -
In Management Console > Cloud Datastore, open the key storage and confirm that a record with the expected user identifier and a non-empty refresh token field has been created.
-
Run the
Check Authoperation manually. In the operation log, confirm that theRefresh Access Tokenoperation completed and thataccess_tokenis non-empty. -
If the browser shows an error page from the provider instead of the confirmation page:
- Confirm that the
redirect_uriproject variable matches exactly the URI registered in the provider console, including scheme, host, and path. OAuth providers reject any mismatch. - Check the API logs in API Manager to confirm the callback request reached the endpoint.
- Confirm that the
-
If the
Exchange Tokenoperation fails with a 400 or 401 response:- Confirm that
client_idandclient_secretare correctly set. - Confirm that
authCodewas not already used. Authorization codes are single-use and expire after a short period (typically 10 minutes).
- Confirm that
-
If the
Refresh Access Tokenoperation fails with a 401 response, the stored refresh token may be invalid or revoked. Re-run theConnectoperation for that user to obtain a new refresh token and update the stored value.