---
url: https://developer-portal.gainsight.com/docs/connectors/authentication.md
description: >-
Connector authentication types — None, API Key, OAuth Client Credentials, JWT,
and OAuth JWT Bearer — and their configuration fields
---
# Authentication
Each connector has an **Authentication Type** that controls how the platform proves your identity to the external API. Credentials are always injected server-side, so they are never exposed to the browser.
Every authentication type follows the same pattern: when a connector executes, the platform applies authentication to the outgoing HTTP request **before** it leaves the server. Depending on the type, this may be as simple as adding a header, or as complex as exchanging a signed token with an OAuth provider.
## Authentication Types
:::tip Which type should you use?
See [Choose an Authentication Type](choose-authentication) for a quick decision guide.
:::
Each type below is documented with the same structure: a description of what it does, a sequence diagram showing the request flow, the configuration fields, an example of what the external API actually receives, and (where applicable) how tokens are managed.
***
### None
No authentication. The platform sends the request exactly as configured, with no additional credentials.
**Request flow**
```mermaid
sequenceDiagram
participant W as Widget
participant P as Platform
participant A as External API
W->>P: SDK connector call
P->>A: Request with configured
headers and query params only
A-->>P: Response
P-->>W: Response data
```
**What the API receives**
```http
GET /v1/public/data HTTP/1.1
Host: api.example.com
Content-Type: application/json
```
No authentication headers or credentials are added. Use this for public APIs that require no authentication.
***
### API Key
Injects a static key-value pair into every request. The key can be placed in either a **header** or a **query parameter**, depending on what the external API expects.
**Request flow**
```mermaid
sequenceDiagram
participant W as Widget
participant P as Platform
participant A as External API
W->>P: SDK connector call
Note over P: Inject API key into
header or query string
P->>A: Request with API key
attached to header or URL
A-->>P: Response
P-->>W: Response data
```
**Configuration**
| Field | Description |
|-------|-------------|
| **Key** | The name to send, e.g. `X-API-Key` or `key` |
| **Value** | The credential value — use a [Secrets and Variables](secrets/), e.g. `get_secret('service_api_key')` |
| **In** | Where to place it: **Header** or **Query Parameter** |
All authentication fields accept [Jinja2](https://jinja.palletsprojects.com/) expressions. To reference a stored secret, type the full expression into the field, for example: {{ get\_secret('service\_api\_key') }}
**What the API receives**
When **In** is set to **Header**:
```http
GET /v1/weather?q=Warsaw HTTP/1.1
Host: api.weatherapi.com
X-API-Key: sk-abc123...
```
When **In** is set to **Query Parameter**:
```http
GET /v1/weather?q=Warsaw&key=sk-abc123... HTTP/1.1
Host: api.weatherapi.com
```
::: tip
Use [Secrets and Variables](secrets/) for the **Value** field instead of hardcoding tokens. This keeps credentials encrypted and out of connector configuration.
:::
***
### OAuth Client Credentials
The platform requests an access token from your OAuth server using your **Client ID** and **Client Secret**, caches it, and injects it as a `Bearer` token on every connector request. This is the standard [OAuth 2.0 Client Credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) flow, designed for server-to-server communication where no user interaction is needed.
**Request flow**
```mermaid
sequenceDiagram
participant W as Widget
participant P as Platform
participant C as Token Cache
participant T as Token Endpoint
participant A as External API
W->>P: SDK connector call
P->>C: Look up cached token
alt Token is cached and valid
C-->>P: Cached Bearer token
else Token missing or expired
C-->>P: Cache miss
P->>T: POST token_url
grant_type=client_credentials
T-->>P: access_token + expires_in
P->>C: Cache token until expiry
end
P->>A: GET /api/resource
Authorization: Bearer [token]
A-->>P: Response
P-->>W: Response data
```
**Configuration**
| Field | Description |
|-------|-------------|
| **Client ID** | The OAuth client identifier — use a [Secrets and Variables](secrets/), e.g. `get_secret('salesforce_client_id')` |
| **Client Secret** | The OAuth client secret — use a [Secrets and Variables](secrets/), e.g. `get_secret('salesforce_client_secret')` |
| **Token URL** | The OAuth token endpoint, e.g. `https://login.salesforce.com/services/oauth2/token` |
| **Scope** | *(optional)* The OAuth scope to request, e.g. `api refresh_token` |
**What the API receives**
When the token is missing or expired, the platform first exchanges credentials at your Token URL:
```http
POST /services/oauth2/token HTTP/1.1
Host: login.salesforce.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=3MVG9...&client_secret=E8B1...
```
The token endpoint responds with an access token:
```json
{
"access_token": "00D5f000000XXXXX!AQcAQH...",
"token_type": "Bearer",
"expires_in": 3600
}
```
The platform then uses this token on every connector request to your external API:
```http
GET /services/data/v59.0/sobjects/Account HTTP/1.1
Host: mycompany.salesforce.com
Authorization: Bearer 00D5f000000XXXXX!AQcAQH...
```
Subsequent requests reuse the cached token — no token exchange happens until it expires.
**Token lifecycle**
The platform caches the token for the duration specified by your OAuth server's `expires_in` response field. On every connector request the platform checks the cache: if the token is missing or expired, a new token is automatically fetched before the request proceeds.
::: warning Service-account responses are not user-scoped
OAuth Client Credentials authenticate as a service account, not as the logged-in user. The external API applies no per-user permission checks, so the response may include records the user is not entitled to see. Filter those records out in the connector definition before they reach the browser. See [Filtering Sensitive Data](filtering-sensitive-data).
:::
***
### JWT
Signs a JSON Web Token using your private key and injects it directly as a `Bearer` token on every request. Unlike OAuth flows, there is no token exchange with an external server -- the signed JWT **is** the credential.
**Request flow**
```mermaid
sequenceDiagram
participant W as Widget
participant P as Platform
participant A as External API
W->>P: SDK connector call
Note over P: Render claims via Jinja2
Sign with private key
P->>A: GET /api/resource
Authorization: Bearer [signed-jwt]
A-->>P: Response
P-->>W: Response data
```
**Configuration**
| Field | Description |
|-------|-------------|
| **Private Key** | The signing key or HMAC secret — use a [Secrets and Variables](secrets/), e.g. `get_secret('signing_private_key')` |
| **Algorithm** | *(optional)* The signing algorithm. Defaults to `RS256`. Supported: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, `EdDSA` |
| **Claims** | A JSON object containing the JWT payload. All values support Jinja2 templates |
| **JWT Headers** | *(optional)* Custom JWT header fields, such as a key identifier (`kid`) |
**Claims example**
```json
{
"iss": "my-service",
"sub": "{{ user.email }}",
"aud": "https://api.example.com",
"exp": "{{ now(3600) }}",
"iat": "{{ now() }}"
}
```
Timestamp claims (`exp`, `iat`, `nbf`) are automatically cast to integers when their rendered values are numeric strings. See [Template Variables](template-variables) for all available context variables and functions.
**What the API receives**
The platform renders your claims with current values, producing a payload like:
```json
{
"iss": "my-service",
"sub": "john@example.com",
"aud": "https://api.example.com",
"exp": 1743007200,
"iat": 1743003600
}
```
This payload is signed with your private key into a compact JWT (`header.payload.signature`), then sent directly as the Bearer token:
```http
GET /v1/protected/resource HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJteS1zZXJ2aWNlIiwic3ViIjoiam9obkBleGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQzMDA3MjAwLCJpYXQiOjE3NDMwMDM2MDB9.signature
```
There is no token exchange — the signed JWT **is** the credential. The external API verifies the signature using your corresponding public key.
**Token lifecycle**
A fresh JWT is signed on **every connector request**. There is no caching -- each request gets a newly generated token with current claim values. This means dynamic claims like `exp` and `iat` always reflect the current time.
***
### OAuth JWT Bearer
Combines JWT signing with an OAuth token exchange. The platform signs a JWT assertion, exchanges it for an access token at your **Token URL**, caches the token, and injects it as `Authorization: Bearer ` for each connector run.
This is common with services like Salesforce that use the [JWT Bearer flow (RFC 7523)](https://datatracker.ietf.org/doc/html/rfc7523).
**Request flow**
```mermaid
sequenceDiagram
participant W as Widget
participant P as Platform
participant C as Token Cache
participant T as Token Endpoint
participant A as External API
W->>P: SDK connector call
P->>C: Look up cached token
alt Token is cached and valid
C-->>P: Cached Bearer token
else Token missing or expired
C-->>P: Cache miss
Note over P: Build JWT assertion
iss=client_id, sub=user
aud=endpoint, exp=now+180s
Note over P: Sign assertion with private key
P->>T: POST token_url
grant_type=jwt-bearer
assertion=[signed-jwt]
T-->>P: access_token
P->>C: Cache token until expiry
end
P->>A: GET /api/resource
Authorization: Bearer [token]
A-->>P: Response
P-->>W: Response data
```
**Configuration**
| Field | Description |
|-------|-------------|
| **Client ID** | The OAuth client identifier (used as the `iss` claim) — use a [Secrets and Variables](secrets/), e.g. `get_secret('salesforce_client_id')` |
| **Private Key** | The key used to sign the JWT assertion — use a [Secrets and Variables](secrets/), e.g. `get_secret('salesforce_private_key')` |
| **Token URL** | The OAuth token endpoint, e.g. `https://login.salesforce.com/services/oauth2/token` |
| **Subject** | The user or service account on whose behalf the token is requested, e.g. `admin@example.com` |
| **Audience** | The intended recipient of the assertion, e.g. `https://login.salesforce.com` |
| **Token TTL** | *(optional)* How long to cache the access token, in seconds. Defaults to `3600` |
| **Algorithm** | *(optional)* The JWT signing algorithm. Defaults to `RS256`. Same options as [JWT](#jwt) above |
| **Additional Claims** | *(optional)* Extra claims merged into the JWT assertion payload, e.g. `{ "scope": "api refresh_token" }` |
| **JWT Headers** | *(optional)* Custom JWT header fields, such as a key identifier |
**What the API receives**
When the token is missing or expired, the platform first builds a short-lived JWT assertion:
```json
{
"iss": "3MVG9...",
"sub": "admin@example.com",
"aud": "https://login.salesforce.com",
"exp": 1743003780,
"iat": 1743003600
}
```
This assertion is signed with your private key and sent to the Token URL:
```http
POST /services/oauth2/token HTTP/1.1
Host: login.salesforce.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiIzTVZHOS4uLiIsInN1YiI6ImFkbWluQGV4YW1wbGUuY29tIn0.signature
```
The token endpoint responds with a standard OAuth access token:
```json
{
"access_token": "00D5f000000XXXXX!AQcAQH...",
"token_type": "Bearer"
}
```
The platform then uses this token on every connector request to your external API:
```http
GET /services/data/v59.0/sobjects/Account HTTP/1.1
Host: mycompany.salesforce.com
Authorization: Bearer 00D5f000000XXXXX!AQcAQH...
```
Note that the JWT assertion is only used during the token exchange — it is never sent to the external API. Subsequent requests reuse the cached access token until it expires.
**Token lifecycle**
The platform caches the access token for the duration specified by **Token TTL**. When the cached token expires, the platform signs a new JWT assertion and exchanges it for a fresh access token automatically.
***
## Authentication object
When configuring connectors programmatically (e.g. via API or code), use these exact JSON structures for each authentication type. All fields support [Jinja2](https://jinja.palletsprojects.com/) templating — use {{ get\_secret('name') }} for sensitive values.
### None type
```json
{
"type": "none"
}
```
No additional config required.
### API Key type
```json
{
"type": "api_key",
"config": {
"key": "X-API-Key",
"value": "{{ get_secret('api_key') }}",
"in": "header"
}
}
```
**Fields:**
* `key` (string): The header or query parameter name, e.g. `X-API-Key` or `api_key`
* `value` (string): The credential value; use {{ get\_secret('name') }} to reference a stored secret
* `in` (string): Either `"header"` or `"query"`
**Note:** The canonical type is `api_key`. The `apikey` value is also accepted as a deprecated alias and normalized to `api_key` — both validate and resolve identically.
### OAuth Client Credentials type
```json
{
"type": "oauth_client_credentials",
"config": {
"client_id": "{{ get_secret('salesforce_client_id') }}",
"client_secret": "{{ get_secret('salesforce_client_secret') }}",
"token_url": "https://login.salesforce.com/services/oauth2/token",
"scope": "api refresh_token"
}
}
```
**Fields:**
* `client_id` (string): OAuth client identifier; use a secret
* `client_secret` (string): OAuth client secret; use a secret
* `token_url` (string): The OAuth token endpoint URL
* `scope` (string, optional): OAuth scope(s) to request
### JWT type
```json
{
"type": "jwt",
"config": {
"private_key": "{{ get_secret('signing_private_key') }}",
"algorithm": "RS256",
"claims": {
"iss": "my-service",
"sub": "{{ user.email }}",
"aud": "https://api.example.com",
"exp": "{{ now(3600) }}"
},
"jwt_headers": {
"kid": "my-key-id"
}
}
}
```
**Fields:**
* `private_key` (string): The signing key or HMAC secret; use a secret
* `algorithm` (string, optional): Signing algorithm; defaults to `RS256`. Supported: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, `EdDSA`
* `claims` (object): JWT payload as a JSON object; all values support Jinja2 templates
* `jwt_headers` (object, optional): Custom JWT header fields
### OAuth JWT Bearer type
```json
{
"type": "oauth_jwt_bearer",
"config": {
"client_id": "{{ get_secret('salesforce_client_id') }}",
"private_key": "{{ get_secret('salesforce_private_key') }}",
"token_url": "https://login.salesforce.com/services/oauth2/token",
"subject": "admin@example.com",
"audience": "https://login.salesforce.com",
"token_ttl": 3600,
"algorithm": "RS256",
"additional_claims": {
"scope": "api refresh_token"
},
"jwt_headers": {
"kid": "my-key-id"
}
}
}
```
**Fields:**
* `client_id` (string): OAuth client identifier; used as the JWT `iss` claim; use a secret
* `private_key` (string): Private key to sign the JWT assertion; use a secret
* `token_url` (string): The OAuth token endpoint URL
* `subject` (string): The user or service account on whose behalf the token is requested
* `audience` (string): The intended recipient of the JWT assertion
* `token_ttl` (integer, optional): Seconds to cache the access token; defaults to `3600`
* `algorithm` (string, optional): JWT signing algorithm; defaults to `RS256`. Same options as JWT above
* `additional_claims` (object, optional): Extra claims merged into the JWT assertion
* `jwt_headers` (object, optional): Custom JWT header fields
## Next Steps
* [Secrets and Variables](secrets/) -- Store credentials with `get_secret()` instead of hardcoding values
* [Headers & Query Parameters](headers-and-query-parameters) -- Add static values alongside authentication
* [Testing & Debugging](testing-and-debugging) -- Verify authentication is working
AUTH FLOW DETAILS:
* API Key Header: sets request.headers\[key] = value
* API Key Query: appends key=value to URL query parameters
* OAuth Client Credentials: POSTs grant\_type=client\_credentials to token\_url, caches access\_token for expires\_in-60 seconds
* JWT: signs claims with private key on every request, injects as Bearer token, no caching
* OAuth JWT Bearer: signs assertion (iss=client\_id, sub=subject, aud=audience, exp=now+180s), POSTs grant\_type=urn:ietf:params:oauth:grant-type:jwt-bearer to token\_url, caches access\_token for token\_ttl-60 seconds