Connecting to External OAuth 2.0 Services
C3 AI applications often need to call external services that use OAuth 2.0. The platform provides a built-in OAuth client API that handles the full token lifecycle — obtaining tokens, refreshing them, and revoking them — without requiring you to implement HTTP requests or token management manually.
This topic covers the client-side OAuth APIs supported by the C3 AI Platform:
- OAuth.AuthorizationServer — describes the external authorization server to connect to.
- OAuth.Request.Token — the parameters for a token request.
- OAuth.Credentials — stores and manages the obtained access token and refresh token.
Note: These APIs are for C3 AI application code that acts as an OAuth client who wants to acquire a token from an OAuth 2.0 provider. This is distinct from the C3 Agentic AI Platform's own OAuth 2.0 authorization server, which issues tokens to clients. See OAuth 2.0 Authorization in the C3 Agentic AI Platform for the server-side documentation.
Key concepts
OAuth.AuthorizationServer
OAuth.AuthorizationServer represents a configured external OAuth provider. It stores the endpoints that the platform uses to obtain and manage tokens. You can construct one in two ways:
- From explicit endpoints using
OAuth.AuthorizationServer.importFromEndpoints(...)when you know the token and authorization endpoint URLs. - From a discovery URL using
OAuth.AuthorizationServer.importFromDiscoveryUrl(...)when the provider publishes an RFC 8414 metadata document (most modern providers do). The platform fetches the document and extracts the relevant endpoints automatically.
A named instance of OAuth.AuthorizationServer is stored in the C3 AI Platform configuration system at the inputted override (defaults to ConfigOverride.APP) level when constructing in the above ways. It can also be retrieved by name.
OAuth.Request.Token
OAuth.Request.Token represents the token request payload. It is what you need to send to the external service in order to be granted a token. It captures all parameters needed by the OAuth 2.0 token endpoint:
| Field | Description |
|---|---|
clientId | OAuth client identifier issued by the provider. |
clientSecret | OAuth client secret. Stored as a secret value. |
grantType | The OAuth grant type (client_credentials, authorization_code, or refresh_token). |
scope | Space-separated list of requested scopes, defined by the authorization server. |
code | Authorization code (used in the authorization code flow only). |
redirectUri | Redirect URI registered with the provider (used in the authorization code flow only). |
codeVerifier | PKCE code verifier (used in the authorization code flow with PKCE only). |
doNotUseAuthorizationHeader | When false (default), credentials are sent via HTTP Basic in the Authorization header per RFC 6749 Section 2.3. Set to true only if the provider cannot accept HTTP Basic. |
Because OAuth.Credentials mixes OAuth.Request.Token, all of these fields are also present directly on a Credentials instance.
OAuth.Credentials
OAuth.Credentials is the central type for working with obtained tokens. It holds the client registration details, the live access token, and an optional refresh token. It provides two member functions:
refresh()— exchanges the refresh token for a new access token.revoke()— revokes the access token (and the refresh token if present) at the provider's revocation endpoint.
Tokens returned by OAuth.AuthorizationServer#token are transient by default; to store them across requests, call setConfig().
Authentication Flows
Client credentials flow (machine-to-machine)
Use this flow when your application authenticates as itself — not on behalf of a user. This is the most common pattern for service-to-service integrations where the provider supports machine-to-machine authentication.
The platform sends the client's credentials to the provider's token endpoint using HTTP Basic authentication by default, which is the most secure approach. Set doNotUseAuthorizationHeader to true only if your provider does not support HTTP Basic.
Authorization code flow with PKCE
Use this flow when a human user must authorize your application interactively. For example, a user grants your application access to their GitHub repositories. PKCE (Proof Key for Code Exchange, RFC 7636) prevents authorization code interception attacks and is required for all public clients.
The platform provides three APIs on OAuth.AuthorizationServer to support this flow:
generateCodeVerifier()— generates a cryptographically random code verifier.computeCodeChallenge(codeVerifier, method)— derives the S256 code challenge from the verifier.pkceAuthorizationUrl(clientId, state, codeChallenge, codeChallengeMethod, redirectUri, scopes)— builds the full authorization URL to redirect the user to.
Configuring an authorization server
Use importFromEndpoints to construct a stored authorization server from known endpoint URLs. The first argument is the desired name of the authorization server, then the token endpoint; the authorization endpoint and revocation endpoint are optional.
var server = OAuth.AuthorizationServer.importFromEndpoints(
"Example Authorization Server", // name of authorization server
"https://example.com/oauth/access_token", // tokenEndpoint (required)
"https://example.com/oauth/authorize", // authorizationEndpoint (required for auth code flow)
"https://example.com/oauth/revoke" // revocationEndpoint (optional)
);For providers that publish an RFC 8414 discovery document, use importFromDiscoveryUrl:
var server = OAuth.AuthorizationServer.importFromDiscoveryUrl(
"Example Authorization Server",
"https://example.com/.well-known/oauth-authorization-server"
);To retrieve the authorization server:
OAuth.AuthorizationServer.forName("<server-name>");Pass true as the second argument to throw an exception rather than return null when the server has not been configured:
var server = OAuth.AuthorizationServer.forName("<server-name>", true);To edit the authorization server:
OAuth.AuthorizationServer.forName("<server-name>")
.withRevocationEndpoint("https://example.com/oauth/revoke")
.setConfig(ConfigOverride.APP);Configuring a credential
Credential objects returned by calling OAuth.AuthorizationServer#token are not automatically stored. To store them, call setConfig() with an optional config override (the override will default to the APP level):
var credentials = server.token("<credential-name>", tokenRequest);
credentials.setConfig();Retrieve them later with forName:
var credentials = OAuth.Credentials.forName("<credential-name>");Obtaining a token
Client credentials flow
// 1. Describe the external authorization server.
var server = OAuth.AuthorizationServer.importFromDiscoveryUrl("<server-name>", "<discovery-url>");
// 2. Build the token request.
var tokenRequest = OAuth.Request.Token.make({
clientId: "<client-id>",
clientSecret: "<client-secret>",
grantType: "client_credentials",
scope: "<provider-specific-scopes>"
});
// 3. Obtain the credentials. The platform sends the token request to the
// provider's token endpoint and returns an OAuth.Credentials instance
// containing the access token.
var credentials = server.token("<credential-name>", tokenRequest);
// 4. Use the access token.
var accessToken = credentials.accessToken();The returned credentials object contains both the accessToken and, if the provider issued one, a refreshToken. You can also read expiresAt to know when the token expires.
Authorization code flow with PKCE
This flow requires user interaction and spans two server requests: one to redirect the user for authorization, and one to exchange the returned code for an access token.
Step 1 – Generate PKCE values and redirect the user
var server = OAuth.AuthorizationServer.importFromEndpoints(
"<server-name>",
"<token-url>", // tokenEndpoint
"<authorization-url>" // authorizationEndpoint
);
// Generate a cryptographically random code verifier.
var codeVerifier = OAuth.AuthorizationServer.generateCodeVerifier();
// Derive the S256 code challenge from the verifier.
var codeChallenge = OAuth.AuthorizationServer.computeCodeChallenge(codeVerifier);
// Generate a random state value to prevent CSRF.
var state = Str.compactUuid();
// Build the authorization URL and redirect the user to it.
var authUrl = server.pkceAuthorizationUrl(
"<client-id>",
state,
codeChallenge,
"S256",
"<redirect-url>",
"<provider-specific-scopes>"
);
// Store codeVerifier and state before redirecting.
// Then redirect the user's browser to authUrl.Step 2 – Exchange the authorization code for a token
After the user authorizes your application, the authorization server redirects to your callback URL with a code query parameter. Exchange that code for an access token:
// codeVerifier and state were stored before the redirect in step 1.
// code and returnedState come from the callback URL query parameters.
// Validate the returned state matches what was sent — prevents CSRF.
if (returnedState !== state) {
throw new Error("OAuth state mismatch — possible CSRF attack.");
}
var tokenRequest = OAuth.Request.Token.make({
clientId: "<client-id>",
clientSecret: "<client-secret>",
grantType: "authorization_code",
code: code, // from callback URL
redirectUri: "<redirect-uri>",
codeVerifier: codeVerifier // from step 1
});
var credentials = server.token("<credential-name>", tokenRequest);
var accessToken = credentials.accessToken();Refreshing a token
When a provider issues a refresh token alongside the access token, you can obtain a new access token without requiring the user to re-authorize:
// credentials is an existing OAuth.Credentials instance.
if (credentials.expiresAt() != null && credentials.expiresAt().isBeforeNow()) {
credentials = credentials.refresh();
}
// credentials.accessToken() now holds a fresh access token.You can also refresh the token using the credential's name:
server.refresh("<credential-name>");As mentioned, the refreshed OAuth.Credentials will not be stored in the configuration system unless .setConfig() is called.
Note: In the case where refresh tokens are not issued by default, credentials.refreshToken() is empty and calling refresh() throws an error. Repeat the authorization code flow to obtain a new token.
Revoking a token
If the authorization server supports RFC 7009 token revocation (a revocation endpoint was provided), you can invalidate the access token and refresh token in the following ways:
credentials.revoke();
server.revoke("<credential-name>");If no revocation endpoint was configured, revoke() throws an error. Check whether your provider supports revocation and pass the endpoint to importFromEndpoints or confirm it is present in the discovery document.
Complete example: calling the GitHub REST API
The following example shows a complete integration: authorizing a user through GitHub and then calling the GitHub REST API to see information about their user account.
Step 1 – Configure the server and redirect the user to GitHub
var gitHubServer = OAuth.AuthorizationServer.importFromEndpoints(
"GitHub Authorization Server",
"https://github.com/login/oauth/access_token", // tokenEndpoint
"https://github.com/login/oauth/authorize" // authorizationEndpoint
);
var codeVerifier = OAuth.AuthorizationServer.generateCodeVerifier();
var codeChallenge = OAuth.AuthorizationServer.computeCodeChallenge(codeVerifier);
var state = Str.compactUuid();
var authUrl = gitHubServer.pkceAuthorizationUrl(
"<github-client-id>",
state,
codeChallenge,
"S256",
"<redirect-uri>",
"read:user"
);
// Redirect the user to authUrl and store codeVerifier and state.Step 2 – Exchange the code for a token and call the GitHub API
// After GitHub redirects back to your callback with ?code=...&state=...
if (returnedState !== state) {
throw new Error("OAuth state mismatch — possible CSRF attack.");
}
// Exchange the authorization code for an access token.
var tokenRequest = OAuth.Request.Token.make({
clientId: "<github-client-id>",
clientSecret: "<github-client-secret>",
grantType: "authorization_code",
code: code,
redirectUri: "<redirect-uri>",
codeVerifier: codeVerifier
});
var credentials = gitHubServer.token("Example GitHub Token", tokenRequest);
// Call the GitHub REST API with the access token.
var response = HttpRequest.builder()
.method("GET")
.url("https://api.github.com/user")
.build()
.withHeader("Authorization", "Bearer " + credentials.accessToken())
.sendSync();
// The following should include information about your GitHub account.
var gitHubUser = response.json();
// If needed, refresh the token.
var credentials = credentials.refresh();Error handling
The following errors can be thrown by the OAuth client APIs:
| Error | Scenario |
|---|---|
| Discovery URL fetch failed | discoveryUrl fetch fails or returns non-200 (check that the discoveryUrl is valid) |
| Missing required endpoint fields | tokenEndpoint or authorizationEndpoint missing (set the endpoints on the OAuth.AuthorizationServer instance) |
| Token request failed with the HTTP status code | Token endpoint returns non-200 (debug with the provided status code or error message) |
| Missing refresh token | refresh() called when refreshToken is empty (please provide a refresh token) |
| No revocation endpoint | revoke() called when no revocation endpoint configured (please provide a revocation endpoint) |
PKCE plain challenge method is not supported | computeCodeChallenge called with plain method (use S256 code challenge method) |
| Missing required fields for authorization code grant type | code or redirectUri missing in authorization code grant (please provide the necessary fields) |
| Please provide a credential or its name in order to perform this operation | Call this API with either an existing credential or the name of an existing credential |
Related content
- OAuth.AuthorizationServer
- OAuth.Credentials
- OAuth.Request.Token
- OAuth.AuthorizationServer.Metadata
- OAuth.Enum.GrantType
- ConfigAclEnabled
- OAuth 2.0 Authorization in the C3 Agentic AI Platform
- The OAuth 2.0 Authorization Framework (RFC 6749)
- Proof Key for Code Exchange (RFC 7636)
- OAuth 2.0 Token Revocation (RFC 7009)
- OAuth 2.0 Authorization Server Metadata (RFC 8414)