Skip to content

Auth & Fetch

The player’s authFetch pipeline handles all network requests made by the player and its built-in plugins. Bearer tokens are injected automatically, 401 responses trigger a single refresh attempt, and 403 responses propagate immediately.

Token storage and security

Tokens are never stored in localStorage or sessionStorage by the core. The player holds the auth config object in memory only, in the options property on the player instance. When you pass a factory function for bearerToken, the function is called on every authenticated request. The token value is not cached between requests.

This means:

  • Page reload clears all tokens, so your app is responsible for restoring auth on mount.
  • The core never reads from or writes to localStorage as part of auth. Any localStorage interaction is your app’s responsibility.
  • A bearerToken factory that returns a stale token is the only way to accidentally replay an expired token, so use a factory that always fetches the current token from your auth SDK.
TypeScript
// Correct: factory always returns the live token
player.setup({
auth: {
bearerToken: () => myAuth.getAccessToken(), // called on every request
refreshOnUnauthenticated: async () => {
await myAuth.refresh(); // resolves when refresh is complete; bearerToken() is re-read automatically
},
},
});

// Risky: static string baked in at setup time
player.setup({
auth: { bearerToken: 'eyJ...' }, // expires, never refreshes automatically
});

Configuration

TypeScript
player.setup({
auth: {
bearerToken: () => myAuth.getAccessToken(),
refreshOnUnauthenticated: async () => {
await myAuth.refresh(); // bearerToken() is re-read automatically after this resolves
},
signRequest: (request) => myHmac.sign(request), // optional, custom signing
headers: { 'X-App-Version': '2.0.0' }, // optional, static headers
},
});

All fields are optional. Without auth, requests are unauthenticated.

Auth config reference

bearerToken

TypeScript
bearerToken?: string | (() => string | Promise<string>)

Token or factory. A factory is called on every authenticated request, use a factory when the token can expire. A static string is applied as-is.

The token is injected as Authorization: Bearer <token>.

refreshOnUnauthenticated

TypeScript
refreshOnUnauthenticated?: () => Promise<void>

Called once when a request returns 401. Invoke your auth SDK’s refresh logic here. After this resolves, the player re-reads bearerToken from the config and retries the failed request exactly once with the new value. If refresh fails, auth:failed fires and the error propagates to the caller.

signRequest

TypeScript
signRequest?: (request: Request) => Request | Promise<Request>

Custom request signer, called after the bearer token is applied. Use for HMAC, API key rotation, or any custom signing scheme.

headers

TypeScript
headers?: Record<string, string | (() => string) | (() => Promise<string>)>

Headers appended to every authenticated request. Each value can be a static string, a sync getter, or an async getter — same forms as bearerToken.

401 vs 403 handling

ResponseBehavior
401refreshOnUnauthenticated called once → request retried. If refresh fails → auth:failed event + error propagates.
403Error propagates immediately. refreshOnUnauthenticated is never called for 403.

Do not lump 401 and 403 together in your error handling. A 403 is an access policy decision, retrying with a refreshed token will not help. Check the server’s entitlement configuration instead.

Auth events:

TypeScript
player.on('auth:refreshed', ({ tokenAcquiredAt }) => {
console.log('Token refreshed at', tokenAcquiredAt);
});

player.on('auth:failed', ({ error }) => {
console.error('Token refresh failed:', error);
});

Retry policy

authFetch retries failed requests using a built-in per-code policy, DEFAULT_RETRY_POLICY — an IRetryPolicy (a map of error-code matcher to RetryConfig). The default is conservative: most codes do not retry, transient ones (timeouts, 5xx, fragment loads) retry a few times with backoff, and 403 never retries.

There is no setup-level retry option. To override retry behaviour for a specific request, pass a RetryConfig as the retry option on this.fetch() inside a plugin:

TypeScript
// inside a plugin's use()
await this.fetch(url, {
retry: { attempts: 5, backoff: 'exponential', baseMs: 200, maxMs: 10_000 },
});

Auth refresh retries are separate from the general retry policy, so refreshOnUnauthenticated is called exactly once, not retried.

Inside plugins: this.fetch

Plugins should use this.fetch<T>(url, options?) instead of the global fetch. The <T> parameter types the parsed response, no casts needed. The options’ responseType ('text' | 'json' | 'arrayBuffer') controls how the body is decoded; pass a custom parser to transform a text body. The player’s auth config and retry policy are applied automatically:

TypeScript
class MyPlugin extends Plugin<...> {
async use(): Promise<void> {
const data = await this.fetch<MyDataShape>(this.opts.endpoint, {
method: 'GET',
responseType: 'json',
});
this.applyData(data);
}
}

If you need the raw Request object before it is dispatched (for example, to add a signature), use signRequest in the auth config rather than building it manually in the plugin.

Updating auth at runtime

Replace auth config after setup, for example, when a long-lived token is refreshed by an external mechanism:

TypeScript
player.auth({
bearerToken: newToken,
});

This merges the provided partial config with the existing auth config. Only the fields you provide are updated.