> For the complete documentation index, see [llms.txt](https://developer.mediarithmics.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.mediarithmics.io/advanced-usages/plugins/creation/destinations-and-authentication/authentication-methods.md).

# Authentication methods

## Authentication methods

This page describes the methods you need to implement in your plugin to handle destination authentication. The SDK routes all authentication-related HTTP calls to these methods automatically.

### Methods per authentication type

| Authentication type        | Methods to implement                                                                        |
| -------------------------- | ------------------------------------------------------------------------------------------- |
| OAuth2                     | `onCreateOAuthRedirectUrl`, `onAuthentication`, `onTestAuthentication`, optional `onLogout` |
| API token / Login-password | `onTestAuthentication`                                                                      |
| Legacy (pre-destination)   | `onAuthenticationStatusQuery`, `onAuthentication`, `onLogout`                               |

### `onCreateOAuthRedirectUrl`

Route: `POST /v1/oauth_redirect_url`

Called when the platform needs an OAuth2 authorization URL. The navigator opens this URL so the user can authorize access with the partner.

```typescript
protected onCreateOAuthRedirectUrl(
  request: CreateOAuthRedirectUrlRequest
): Promise<CreateOAuthRedirectUrlPluginResponse>
```

#### Input: `CreateOAuthRedirectUrlRequest`

| Field                 | Type     | Description                                                       |
| --------------------- | -------- | ----------------------------------------------------------------- |
| `feed_destination_id` | `string` | The destination being authenticated.                              |
| `plugin_version_id`   | `string` | The plugin version, used to build the mediarithmics callback URL. |

#### Return: `CreateOAuthRedirectUrlPluginResponse`

| Field       | Type     | Description                                        |
| ----------- | -------- | -------------------------------------------------- |
| `login_url` | `string` | The partner authorization URL the navigator opens. |

{% hint style="info" %}
The SDK returns HTTP 500 when `feed_destination_id` is missing. It also validates the same query parameter in `login_url`. The default implementation returns HTTP 500. Override this method for OAuth2.
{% endhint %}

#### Code example

```typescript
protected async onCreateOAuthRedirectUrl(
  request: core.CreateOAuthRedirectUrlRequest,
): Promise<core.CreateOAuthRedirectUrlPluginResponse> {
  const { clientId, publicKey } = await this.getCredentials();
  const state = encodeURIComponent(
    this.encryptWithPublicKey(`feed_destination_id:${request.feed_destination_id}`, publicKey),
  );
  const redirectUri = encodeURIComponent(
    `https://navigator.mediarithmics.com/v2/plugin_versions/${request.plugin_version_id}/auth_callback`,
  );

  return {
    login_url:
      `https://partner.example.com/oauth2/authorize?client_id=${clientId}` +
      `&response_type=code&redirect_uri=${redirectUri}&state=${state}` +
      `&feed_destination_id=${request.feed_destination_id}`,
  };
}
```

### `onAuthentication`

Route: `POST /v1/authentication`

Called after the partner redirects the user to the callback URL. The plugin exchanges the OAuth2 authorization code for a refresh token.

This route also supports the legacy datamart-scoped flow. In that flow, the plugin stores credentials itself.

```typescript
protected onAuthentication(
  request: ExternalSegmentAuthenticationRequest
): Promise<ExternalSegmentAuthenticationResponse>
```

#### Input: `ExternalSegmentAuthenticationRequest`

| Field                 | Type                                   | Description                                                 |
| --------------------- | -------------------------------------- | ----------------------------------------------------------- |
| `user_id`             | `string`                               | The user who triggered the authentication.                  |
| `plugin_version_id`   | `string`                               | The plugin version.                                         |
| `params`              | `{ [key: string]: string }` (optional) | Callback parameters. For OAuth2, `code` and `state`.        |
| `feed_destination_id` | `string` (optional)                    | Carried by the platform. It does not drive the vault write. |

#### Return: `ExternalSegmentAuthenticationResponse`

| Field                 | Type                                       | Description                                                                               |
| --------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `status`              | `'ok'` \| `'error'` \| `'not_implemented'` | Result of the authentication. Maps to HTTP 200, 500, or 400.                              |
| `message`             | `string` (optional)                        | Error detail.                                                                             |
| `refresh_token`       | `string` (optional)                        | Required when returning `feed_destination_id`. The SDK removes it from the HTTP response. |
| `feed_destination_id` | `string` (optional)                        | Return this value to write OAuth2 credentials to the vault.                               |

The SDK writes credentials only when the response returns `feed_destination_id`. Extract it from the decrypted OAuth `state`. When present, also return `refresh_token`. The SDK stores it as `{ scheme: 'OAUTH2', credentials: { refresh_token } }`.

#### Code example

```typescript
protected async onAuthentication(
  request: core.ExternalSegmentAuthenticationRequest,
): Promise<core.ExternalSegmentAuthenticationResponse> {
  const code = request.params?.['code'];
  const state = request.params?.['state'];
  if (!code || !state) {
    return { status: 'error', message: 'Missing code or state in request.params' };
  }

  const { privateKey } = await this.getCredentials();
  const { feed_destination_id } = this.parseState(
    this.decryptWithPrivateKey(state, privateKey),
  );
  if (feed_destination_id) {
    const refreshToken = await this.exchangeCodeForRefreshToken(
      code,
      request.plugin_version_id,
    );
    return { status: 'ok', refresh_token: refreshToken, feed_destination_id };
  }

  await this.storeLegacyRefreshToken(state, code);
  return { status: 'ok' };
}
```

### `onTestAuthentication`

Route: `POST /v1/test_authentication`

Called to validate a destination's stored credentials. The platform uses the result to set the destination status.

```typescript
protected onTestAuthentication(
  request: TestAuthenticationRequest,
  credentials: FeedDestinationCredentials
): Promise<TestAuthenticationPluginResponse>
```

#### Input: `TestAuthenticationRequest`

| Field                 | Type     | Description                                       |
| --------------------- | -------- | ------------------------------------------------- |
| `feed_destination_id` | `string` | The destination whose credentials must be tested. |

#### Input: `credentials`

The SDK fetches credentials from `GET /v1/feed_destinations/{id}/credentials`. It does not cache this fetch. A failure returns HTTP 500 and does not call the method.

#### Return: `TestAuthenticationPluginResponse`

| `status`                | HTTP code | When to use                                                                                                                                                                                      |
| ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `'ok'`                  | 200       | Credentials are valid. Destination status is set to **Ready**.                                                                                                                                   |
| `'invalid_credentials'` | 401       | The partner explicitly rejected the credentials (e.g. token revoked). Destination status is set to **Connection Error**. Use this to distinguish credential rejection from operational failures. |
| `'error'`               | 500       | Unexpected or operational error (network failure, partner API down).                                                                                                                             |
| `'not_implemented'`     | 400       | Method not overridden. Default SDK behavior.                                                                                                                                                     |

`message` is optional. It contains the detail shown to the user.

#### Code example

```typescript
protected async onTestAuthentication(
  request: core.TestAuthenticationRequest,
  credentials: core.FeedDestinationCredentials,
): Promise<core.TestAuthenticationPluginResponse> {
  const refreshToken = credentials?.credentials?.refresh_token as string | undefined;
  if (!refreshToken) {
    return {
      status: 'invalid_credentials',
      message: 'No refresh_token in feed destination credentials.',
    };
  }

  try {
    const accessToken = await this.requestAccessTokenFromRefreshToken(refreshToken);
    return accessToken
      ? { status: 'ok' }
      : { status: 'invalid_credentials', message: 'Could not obtain an access token.' };
  } catch (err) {
    return { status: 'invalid_credentials', message: (err as Error).message };
  }
}
```

### `onAuthenticationStatusQuery` (legacy flow)

Route: `POST /v1/authentication_status_queries`

Called to check authentication in the datamart-scoped flow. Return a login URL when the user must authenticate.

```typescript
protected onAuthenticationStatusQuery(
  request: ExternalSegmentAuthenticationStatusQueryRequest,
): Promise<ExternalSegmentAuthenticationStatusQueryResponse>
```

#### Input: `ExternalSegmentAuthenticationStatusQueryRequest`

| Field               | Type                          |
| ------------------- | ----------------------------- |
| `datamart_id`       | `string`                      |
| `plugin_version_id` | `string`                      |
| `user_id`           | `string`                      |
| `segment_id`        | `string` (optional)           |
| `properties`        | `PluginProperty[]` (optional) |

#### Return: `ExternalSegmentAuthenticationStatusQueryResponse`

| Field     | Type                                                                           | Description                                                 |
| --------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| `status`  | `'authenticated'` \| `'not_authenticated'` \| `'error'` \| `'not_implemented'` | Maps to HTTP 200, 200, 500, or 400.                         |
| `message` | `string` (optional)                                                            | Error detail.                                               |
| `data`    | `{ login_url?: string; [key: string]: any }` (optional)                        | Return `login_url` with `not_authenticated` to start OAuth. |

This route supports the datamart-scoped flow. Destination authentication uses `onCreateOAuthRedirectUrl` and `onTestAuthentication`.

### `onLogout`

Route: `POST /v1/logout`

Called when the user disconnects authentication in the datamart-scoped flow. Revoke partner credentials and remove any stored state.

```typescript
protected onLogout(
  request: ExternalSegmentLogoutRequest
): Promise<ExternalSegmentLogoutResponse>
```

#### Input: `ExternalSegmentLogoutRequest`

| Field               | Type     |
| ------------------- | -------- |
| `user_id`           | `string` |
| `plugin_version_id` | `string` |
| `datamart_id`       | `string` |

This route has no `feed_destination_id`. It supports the datamart-scoped flow.

#### Return: `ExternalSegmentLogoutResponse`

| Field     | Type                                       | Description                    |
| --------- | ------------------------------------------ | ------------------------------ |
| `status`  | `'ok'` \| `'error'` \| `'not_implemented'` | Maps to HTTP 200, 500, or 400. |
| `message` | `string` (optional)                        | Error detail.                  |

### SDK helper methods

The base plugin class exposes the following helpers for credential management. You can call them from any method in your plugin.

#### `fetchFeedDestinationCredentials`

Fetches the credentials stored in the vault for a given destination. Throws a `ResourceNotFoundError` if no credentials exist.

```typescript
protected async fetchFeedDestinationCredentials(
  feedDestinationId: string
): Promise<FeedDestinationCredentials>
```

#### `fetchFeedDestinationCredentialsOptional`

Same as above, but returns `undefined` instead of throwing when no credentials are found. Useful to implement a fallback on legacy credential files during migration.

```typescript
protected async fetchFeedDestinationCredentialsOptional(
  feedDestinationId: string
): Promise<FeedDestinationCredentials | undefined>
```

#### `upsertFeedDestinationCredentials`

Creates or updates credentials in the vault for a given destination. In the standard flow, the SDK calls this automatically from `onAuthentication`. Call it manually only when managing credentials outside the authentication flow.

```typescript
protected async upsertFeedDestinationCredentials(
  feedDestinationId: string,
  credentials: FeedDestinationCredentials
): Promise<void>
```

### Note on `instanceContextBuilder`

When a request carries a `feed_destination_id`, the SDK fetches the destination credentials from the vault and passes them as an optional second argument to `instanceContextBuilder`. Use them to initialize your partner client instead of reading from a credential file:

```typescript
protected async instanceContextBuilder(
  feedId: string,
  feedDestinationCredentials?: core.FeedDestinationCredentials,
): Promise<InstanceContext> {
  const ctx = await super.instanceContextBuilder(feedId, feedDestinationCredentials);

  // Use vault credentials when available, fall back to legacy credential file
  const refreshToken =
    (feedDestinationCredentials?.credentials?.refresh_token as string) ??
    (await this.fetchLegacyRefreshToken(ctx));

  const partnerClient = new PartnerClient({ refreshToken });

  return { ...ctx, partnerClient };
}
```

The same credentials are also passed to `onUserSegmentUpdate`, `onTroubleshoot`, and `onDynamicPropertyValuesQuery`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developer.mediarithmics.io/advanced-usages/plugins/creation/destinations-and-authentication/authentication-methods.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
