Skip to content
D
Documentation

Authenticate requests

how-to
2 min readUpdated

Use ky hooks to attach an authentication header to the initial request and refresh credentials before a retry. The example below sends a bearer token to one trusted API origin and retries a rejected GET once with a fresh token.

1. Attach a token and refresh it on a 401

Install Ky in your project:

bash
npm install ky

Run this module in the browser or Node.js with Fetch support. Replace <access-token> and <refresh-token> with your credentials, and change the API URLs to your service. This example expects /auth/refresh to accept a JSON refresh token and return the new access token as plain text. Adapt that request and body reader to your service's contract.

Use beforeRequest to set request.headers. It runs once before retry handling begins, not before every attempt. Use beforeRetry to update the header on a confirmed retry, and isHTTPError to distinguish an HTTP rejection from other failures.

ts
import ky, {isHTTPError} from 'ky';

const trustedOrigin = 'https://api.example.com';
let accessToken = '<access-token>';
const refreshToken = '<refresh-token>';

function requireTrustedOrigin(request: Request): void {
	if (new URL(request.url).origin !== trustedOrigin) {
		throw new Error('Refusing to send credentials to an untrusted origin');
	}
}

const api = ky.extend({
	credentials: 'omit',
	redirect: 'error',
	retry: {
		limit: 1,
		methods: ['get'],
		statusCodes: [401],
	},
	hooks: {
		beforeRequest: [({request}) => {
			requireTrustedOrigin(request);
			request.headers.set('Authorization', `Bearer ${accessToken}`);
		}],
		beforeRetry: [async ({request, error}) => {
			requireTrustedOrigin(request);
			if (!isHTTPError(error) || error.response.status !== 401) {
				return;
			}

			accessToken = await ky.post(`${trustedOrigin}/auth/refresh`, {
				json: {refreshToken},
				credentials: 'omit',
				redirect: 'error',
				retry: 0,
			}).text();
			request.headers.set('Authorization', `Bearer ${accessToken}`);
		}],
	},
});

async function main(): Promise<void> {
	const response = await api.get(`${trustedOrigin}/users/me`);
	console.log(await response.text());
}

main().catch(console.error);

The refresh call uses ky, not api, so it does not inherit the authentication hooks. In your network inspector or server logs, look for an initial GET with the access token. If your service rejects it with 401, look for a refresh POST followed by a GET with the replacement token. The call returns the final response; the log contains your service's response body, not the refresh token. If authentication still fails, the request rejects after the single retry.

401 is not a default retry status. The explicit statusCodes: [401] makes it eligible here; beforeRetry does not itself decide whether to retry. See Retry failed requests for broader retry policies.

2. Keep credentials within the intended origin

The example checks the actual request.url before adding or refreshing the header, keeps the retry at the same URL, and rejects redirects with redirect: 'error'. credentials: 'omit' disables Fetch-managed credentials; the bearer header is still set explicitly.

Replacement retry requests are used as-is and are not sanitized. Changing origins can forward credentials unintentionally. Before returning a Request from beforeRetry, or passing one to ky.retry({request}), remove every credential you do not want forwarded. For example, delete Authorization and any service-specific API-key headers from a copied Headers object and construct the replacement with credentials: 'omit'. Do not copy a sensitive request body to another origin either.

Returning a replacement Request from beforeRetry skips the remaining beforeRetry hooks. Sanitize it before returning it; do not rely on a later hook to strip credentials. Ky replaces its signal with the managed signal for timeout and abort handling, but does not strip its authentication headers.

Options that matter

OptionTypeDefaultWhat it does
hooks.beforeRequestArray of hook functions[]Changes the initial outgoing request; retryCount is always 0.
hooks.beforeRetryArray of hook functions[]Changes a confirmed retry; retryCount starts at 1.
retry.limitnumber2Bounds retries. The example allows one retry.
retry.methodsReadonly array of HTTP methods['get', 'put', 'head', 'delete', 'options', 'trace', 'query']Restricts automatic retries to eligible methods.
retry.statusCodesreadonly number[][408, 413, 429, 500, 502, 503, 504]Selects eligible HTTP failures. The example selects 401.

Was this page helpful?

Authenticate requests — ky · GPT-6.1 Sol