# Vault OAuth protection

OAuth protection stops a Vault connection from being completed in someone else's session or handed to a page you don't control. Each application registers the pages Vault may send users back to and the sites that may open or embed Vault. With protection enforced, Vault rejects any OAuth flow that uses an unregistered value, ties each flow to the browser that started it, and keeps a new connection unusable until it is confirmed.

You manage it on the **OAuth protection** card under **Configuration → Vault** in the [Apideck dashboard](https://platform.apideck.com/configuration/vault-settings).

## Off and Enforced

The **Enforce protection** switch has two modes. Check it on your application: new applications can start with protection enforced.

|                                                     | Off                               | Enforced                                         |
| --------------------------------------------------- | --------------------------------- | ------------------------------------------------ |
| Unregistered redirect URL                           | Allowed, and listed on the card   | Rejected with `422` before any connection exists |
| Unregistered site opening or embedding Vault        | Allowed, and listed on the card   | Rejected with `422`                              |
| Session created with an unregistered `redirect_uri` | Created                           | Rejected with `400`, no session is created       |
| Flow finished in a different browser                | Allowed                           | Rejected at the callback                         |
| New connection                                      | Callable right after the callback | Unconfirmed until it is confirmed, then callable |

Turning protection on or off never changes connections that already exist.

## Register redirect URLs and sites

### Redirect URLs

Redirect URLs are the pages Vault may send users back to after connecting: the `redirect_uri` you pass to [authorize](/apis/vault/reference#operation/connectionsAuthorize) or to [session creation](/apis/vault/reference#operation/sessionsCreate).

- An entry allows that exact page. Trailing slashes, the query string and the fragment are ignored.
- An entry ending in `/*` also allows every page under it, on a path segment boundary.
- An entry without a path allows only the root page of that host.
- A `*` as the whole first label of the host allows exactly one subdomain.
- `https` is required, except for `http://localhost` and `http://127.0.0.1`. Without a port, a local entry allows any port.
- The authorization landing page set under **Landing Pages** counts as a registered redirect URL.
- Hosted Vault's own pages are always allowed and can't be registered.

| Entry                                    | Allows                                                                              | Does not allow                                                                   |
| ---------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `https://app.example.com/callback`       | `https://app.example.com/callback`, `https://app.example.com/callback/?tab=1`       | `https://app.example.com/callback/done`, `https://app.example.com/callback.evil` |
| `https://app.example.com/integrations/*` | `https://app.example.com/integrations`, `https://app.example.com/integrations/xero` | `https://app.example.com/integrations-old`                                       |
| `https://*.example.com/callback`         | `https://eu.example.com/callback`                                                   | `https://example.com/callback`, `https://a.b.example.com/callback`               |
| `http://localhost/callback`              | `http://localhost:3000/callback`                                                    | `https://localhost/callback`                                                     |

### Embedding sites

Embedding sites are the sites that may open or embed Vault: the page that loads Vault JS or react-vault. Only the scheme, host and port count, for example `https://app.example.com`. A `*` as the whole first label allows one subdomain, as in `https://*.example.com`, but never a whole public suffix such as `https://*.co.uk`.

Each list holds up to 50 entries. Redirect URLs can be up to 2,048 characters and sites up to 255.

### What to register

| How you use Vault                                                                                                     | Register                                                                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Vault JS](/guides/vault), [Vault React](/guides/vault-react) or `@apideck/react-vault` on your site                  | Your site under **Embedding sites**, for example `https://app.example.com`. The popup returns to Hosted Vault, so no redirect URL is needed.                             |
| [Hosted Vault](/guides/authorize-connections#option-2-redirecting-to-the-hosted-vault-connection-page)                | The session's `redirect_uri` under **Redirect URLs**, if you set one.                                                                                                    |
| Your own UI with the [`authorize_url`](/guides/authorize-connections#option-3-completely-built-into-your-application) | The `redirect_uri` you append under **Redirect URLs**, then [confirm connections](#confirm-connections). A Hosted Vault page with `?jwt=` is rejected as a redirect URL. |

## Unregistered values

While protection is off, Vault records the redirect URLs and sites it sees that aren't registered. When there are any, the card shows a warning, and **Review** lists each value with how often it was seen. Use it to complete your lists before you enforce.

> **Register only what you recognise**
>
> These values come from requests that anyone can make. A value you don't recognise may be someone
> probing your application, so don't register it.

## Before you enforce

Turning the switch on asks you to check these setups first.

### Own redirect URL

Some connectors use your own OAuth redirect URL instead of `https://unify.apideck.com/vault/callback`, for example a white-label domain registered with the provider. With protection enforced, the flow has to finish in the browser that started it. Your redirect URL must send the browser on to `https://unify.apideck.com/vault/callback` with the full query string, using a browser redirect such as a `302`. Forwarding the callback from your server fails, because the request no longer comes from the user's browser.

### Hosted Vault sessions

With protection enforced, the `redirect_uri` you pass to [session creation](/apis/vault/reference#operation/sessionsCreate) must be a registered redirect URL, or the request fails with `400` and no session is created. A session with `settings.auto_redirect` sends users back to that `redirect_uri` once their connection is ready. Without a `redirect_uri`, users can still connect, but they stay in Vault afterwards.

### react-vault

If you embed `@apideck/react-vault` yourself, upgrade to 0.24.0 or later before you enforce. Earlier versions don't send the site that opened the popup, so every connection attempt is rejected. Vault JS, [Vault React](/guides/vault-react) (`@apideck/vault-react`, which loads Vault JS) and Hosted Vault are updated by Apideck.

### Confirm connections

With protection enforced, a new connection stays unconfirmed, and can't be used, until it is confirmed. Vault JS, react-vault and Hosted Vault confirm it for you. If your `redirect_uri` is a page of your own app, your app confirms it.

The callback redirects to your `redirect_uri` with the confirm details in the URL fragment:

```
https://app.example.com/callback#confirm_token=550e8400-e29b-41d4-a716-446655440000&service_id=quickbooks&unified_api=accounting
```

Read the fragment on that page and send the values to your backend:

```js meta=Browser
const params = new URLSearchParams(window.location.hash.slice(1))
const confirmToken = params.get('confirm_token')
const serviceId = params.get('service_id')
const unifiedApi = params.get('unified_api')
```

Your backend then calls [`POST /vault/connections/{unified_api}/{service_id}/confirm`](/apis/vault/reference#operation/connectionsConfirm):

```bash meta=Request
curl -X POST 'https://unify.apideck.com/vault/connections/accounting/quickbooks/confirm' \
  -H 'Authorization: Bearer <APIDECK_API_KEY>' \
  -H 'x-apideck-app-id: <APP_ID>' \
  -H 'x-apideck-consumer-id: acme-corp' \
  -H 'Content-Type: application/json' \
  -d '{ "confirm_token": "550e8400-e29b-41d4-a716-446655440000" }'
```

The confirm token is single use and expires 30 minutes after the callback. If your `redirect_uri` already has a fragment, such as `#/connections`, the values are appended after `&`, and `URLSearchParams` still reads them.

## Errors

| Error                                                                            | Status                                            | When                                                                                        | Fix                                                                                         |
| -------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`OAuthUnregisteredRedirectUriError`](/errors#oauthunregisteredredirecturierror) | `422`                                             | The authorize `redirect_uri` isn't registered                                               | Add it under **Redirect URLs**                                                              |
| [`OAuthUnregisteredOriginError`](/errors#oauthunregisteredoriginerror)           | `422`                                             | The site that opened or embeds Vault isn't registered, or an outdated client didn't send it | Add the site under **Embedding sites**, or upgrade react-vault                              |
| [`OAuthBrowserBindingError`](/errors#oauthbrowserbindingerror)                   | Sent to your `redirect_uri` as an error, or `422` | The callback came from a different browser than the one that started the flow               | Start the connection again in one browser; check your [own redirect URL](#own-redirect-url) |

An unregistered redirect URL or site is answered with JSON and is never redirected to.

## Next steps

- [Vault JS](/guides/vault) and [Vault React](/guides/vault-react) for embedding Vault
- [Authorize Connections](/guides/authorize-connections) for the ways users can connect
- [Connection States](/guides/connection-states) for what happens after a connection is confirmed
- [Confirm Connection](/apis/vault/reference#operation/connectionsConfirm) in the API reference
