# Authenticated requests

Load protected PDFs with cookies, headers or your own HTTP client, and handle expired sessions and HTTP errors your way.

URL sources are fetched by the viewer with `fetch` and handed to PDF.js as bytes. That lets you shape the request, and lets the viewer see HTTP statuses.

## Basics

`requestInit` adds `fetch` options such as credentials and headers to the request.

**Demo: Send cookies and a header**

```tsx
import { PdfViewer } from '@kingtech/react-pdf-viewer';

export default function Demo() {
  // Extra fetch options for URL sources: send cookies and a custom header.
  return (
    <PdfViewer
      source="/samples/letter-3pages.pdf"
      requestInit={{ credentials: 'include', headers: { 'X-Requested-With': 'react-pdf-viewer' } }}
    />
  );
}
```

### Your own HTTP client

`fetcher(url, init)` replaces `fetch` entirely, for example with a client that adds and refreshes tokens. `init.signal` is aborted when the source changes.

**Demo: A bearer token and a handled 401**

```tsx
import { PdfViewer } from '@kingtech/react-pdf-viewer';
import { useCallback, useState } from 'react';

const TOKEN = 'demo-token';

/** Stand-in for your API: rejects requests without a bearer token. */
async function api(url: string, init: RequestInit): Promise<Response> {
  if (new Headers(init.headers).get('Authorization') !== `Bearer ${TOKEN}`) {
    return new Response(JSON.stringify({ title: 'Unauthorized', status: 401 }), {
      status: 401,
      headers: { 'Content-Type': 'application/problem+json' },
    });
  }
  return fetch(url, { signal: init.signal ?? null });
}

export default function Demo() {
  const [signedIn, setSignedIn] = useState(false);
  const [notice, setNotice] = useState('');

  const fetcher = useCallback(
    (url: string, init: RequestInit) =>
      api(url, { ...init, headers: signedIn ? { Authorization: `Bearer ${TOKEN}` } : {} }),
    [signedIn],
  );

  return (
    <>
      <p className="demo-controls">
        <label>
          <input
            type="checkbox"
            checked={signedIn}
            onChange={(event) => {
              setSignedIn(event.target.checked);
              setNotice('');
            }}
          />
          Signed in
        </label>
        {notice && <span role="status">{notice}</span>}
      </p>
      <PdfViewer
        // The fetcher applies to the next load: remount to load again with the new token.
        key={String(signedIn)}
        source="/samples/letter-3pages.pdf"
        fetcher={fetcher}
        onHttpError={(response) => {
          if (response.status === 401) {
            setNotice('401 Unauthorized: sign in to load the document.');
            return true; // handled: empty state, no error view, no onError
          }
          return false;
        }}
      />
    </>
  );
}
```

### Handling HTTP errors

`onHttpError(response)` runs for every non-2xx response. Return `true` when you have handled it (for example by redirecting to sign-in): the viewer shows its empty state and does not call `onError`. Otherwise the error view appears with the message from `getHttpErrorMessage`.

```tsx
<PdfViewer
  source={url}
  requestInit={{ credentials: 'include' }}
  onHttpError={(response) => {
    if (response.status === 401) {
      signIn({ returnTo: window.location.href });
      return true;
    }
  }}
/>
```

By default the message is read from an RFC 9457 (formerly 7807) problem body (`detail`, then `title`), then a JSON string body, then `Failed to fetch PDF: {status} {statusText}`.

## Cross-origin documents

Reading a PDF from another origin requires the server to send `Access-Control-Allow-Origin` for your origin; sending cookies additionally requires `credentials: 'include'` and `Access-Control-Allow-Credentials: true`. A blocked request surfaces as a `NETWORK_ERROR`, because browsers do not tell scripts why a request was blocked. When you cannot change the server, proxy the file through your own origin.

## Customization

`getHttpErrorMessage(response)` writes the error message for your API's statuses. Return `undefined` to keep the default.

**Demo: A friendlier 404 message**

```tsx
import { PdfViewer } from '@kingtech/react-pdf-viewer';

export default function Demo() {
  return (
    <PdfViewer
      source="/samples/archived-report.pdf"
      getHttpErrorMessage={
        async (response) =>
          response.status === 404
            ? 'This report has been archived. Ask your administrator to restore it.'
            : undefined // use the default message for other statuses
      }
    />
  );
}
```

To replace the whole error view, see [Error states](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/error-states/) and the [customization guide](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/customization/).

## Advanced / escape hatch

If requests need logic the viewer cannot express (multi-step downloads, signed URLs fetched from another endpoint), fetch the bytes yourself and pass them as a `Blob` or `ArrayBuffer` [source](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/document-sources/). [`usePdfDocument`](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless/) accepts the same request options.

## Limitations

- **Request options apply to the next load.** Changing `requestInit` or `fetcher` does not reload the current document. Workaround: change the viewer's `key`, or call `reload()` on the [imperative API](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/imperative-api/).
- **No range requests.** The whole file is downloaded before rendering starts. Workaround: none within the viewer; keep very large documents split server-side.
- **Error handlers share one response.** `onHttpError` and `getHttpErrorMessage` receive the same `Response`, and the default message reads a clone of its body. Workaround: read bodies with `response.clone().json()` in your handlers so the default message still works.

## API

- [PdfRequestOptions](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/pdf-request-options.md)

---

## PdfRequestOptions reference

Controls how string and URL sources are fetched: extra `fetch` options, a replacement fetcher and the handling of HTTP errors.

### Used by

- [Authenticated requests](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/authenticated-requests.md)

### Import

```ts
import type { PdfRequestOptions } from '@kingtech/react-pdf-viewer';
```

### Properties

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `requestInit` | `RequestInit` | — | No | Extra `fetch` options for URL sources: headers, `credentials`, `mode`, … |
| `fetcher` | `(url: string, init: RequestInit) => Promise<Response>` | — | No | Replaces `fetch` entirely, e.g. with an authenticated HTTP client. |
| `onHttpError` | `(response: Response) => boolean \| void \| Promise<boolean \| void>` | — | No | Called for non-2xx responses. Return `true` when handled (e.g. after redirecting to sign-in): the viewer shows its empty state and does not call `onError`. |
| `getHttpErrorMessage` | `(response: Response) => Promise<string>` | — | No | Builds the `HTTP_ERROR` message. Return `undefined` for the default: an RFC 7807 `detail`, then `title`, then a JSON string body, then `Failed to fetch PDF: {status} {statusText}`. |

### Source

[packages/react-pdf-viewer/src/types.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/types.ts#L116)

