---
title: Well-Known Pages
---

AuthProxy serves built-in HTML pages for a range of conditions — provider selection,
invitation errors, tenant errors, and generic HTTP errors.
Every page can be **overridden** by mounting a directory of custom pages into the container.

---

## Built-in pages

The following pages are included with AuthProxy and are served automatically when the corresponding
condition is detected:

| File name | Condition | HTTP status |
|-----------|-----------|-------------|
| `404.html` | The requested resource was not found. | 404 |
| `403.html` | The identity resolver denied access. | 403 |
| `not-authorized.html` | The caller is signed in but does not satisfy the claim requirements declared in [`Cratis:AuthProxy:Authorization`](/authproxy/configuration/authorization/). | 403 |
| `tenant-not-found.html` | The resolved tenant does not exist in the platform (see [Tenant verification](/authproxy/configuration/tenancy/#tenant-verification)). | 404 |
| `select-provider.html` | A protected resource was requested and multiple identity providers are configured, or a sign-in failed (see [Failed Sign-ins](/authproxy/configuration/failed-sign-ins/)). The page reads the `.cratis-providers` cookie to render a sign-in button for each available provider, and shows a message when a `reason` query parameter is present. | 200 |
| `select-tenant.html` | Tenant selection is enabled and the authenticated user has not selected a tenant yet. The page reads the `.cratis-tenants` cookie to render selectable tenants. | 200 |
| `invitation-expired.html` | The JWT token on an invite link has passed its expiry time. Served in either phase — when the link is followed (Phase 1) and when the token is re-validated at the exchange (Phase 2). | 401 |
| `invitation-invalid.html` | The JWT token on an invite link is malformed or has an invalid signature. Served in either phase — when the link is followed (Phase 1) and when the token is re-validated at the exchange (Phase 2). | 401 |
| `invitation-select-provider.html` | A valid invite link was followed and multiple identity providers are configured. The page reads the `.cratis-providers` cookie to render a sign-in button for each available provider. | 200 |
| `invitation-subject-already-exists.html` | The authenticated user's subject is already associated with an existing account during invite exchange (Phase 2). | 409 |
| `invitation-email-unavailable.html` | Gateway email binding is enabled (`Invite.EmailClaim`), but the identity provider supplied no authenticated-session email address (Phase 2). | 403 |
| `invitation-email-mismatch.html` | Gateway email binding is enabled (`Invite.EmailClaim`), and the identity provider supplied another address or explicitly reported `email_verified=false` (Phase 2). | 403 |
| `link-select-provider.html` | The embeddable provider-selection page of the [credential-link flow](/authproxy/configuration/link/), served at `/.cratis/link`. Lists providers, opens the chosen provider's link challenge in a top-level window, and reports the outcome to its embedding parent. | 200 |
| `link-complete.html` | A credential link completed, at `/.cratis/link/complete`. Broadcasts the completion on the link flow's `BroadcastChannel` and closes its window. | 200 |
| `link-failed.html` | A credential link did not complete — the provider round-trip failed or the exchange was refused (see [Credential Linking](/authproxy/configuration/link/)). Broadcasts the failure so an embedding selection page can offer a retry. | 403 |

---

## Overriding pages

Mount a directory into the container and point `Cratis:AuthProxy:PagesPath` at the mount path.
AuthProxy looks up each page by its conventional file name inside that directory.
Any page file that is present overrides the built-in default; missing files fall back to the built-in version.

```json
{
  "Cratis": {
    "AuthProxy": {
      "PagesPath": "/mnt/pages"
    }
  }
}
```

Equivalent environment variable:

```
Cratis__AuthProxy__PagesPath=/mnt/pages
```

### Container mount example (Docker Compose)

```yaml
services:
  authproxy:
    image: cratis/authproxy:latest
    volumes:
      - ./my-pages:/mnt/pages
    environment:
      Cratis__AuthProxy__PagesPath: /mnt/pages
```

### Page assets

Pages can reference stylesheets, images, and other assets.  
Place asset files in the same pages directory — they are served at the `/_pages/` URL prefix.

```html
<!-- inside your custom page -->
<link rel="stylesheet" href="/_pages/styles.css" />
<img src="/_pages/logo.svg" alt="Logo" />
```

---

## Provider selection pages

`select-provider.html` and `invitation-select-provider.html` both serve the same purpose —
letting the user choose an identity provider when multiple are configured — but they are
triggered by different flows:

| Page | Triggered by |
|------|-------------|
| `select-provider.html` | Direct navigation to a protected resource when not yet authenticated. |
| `invitation-select-provider.html` | Following a valid invite link. |

Both pages receive provider data via the `.cratis-providers` cookie and work identically
from a customization standpoint.  See [Provider Selection Pages](/authproxy/configuration/invitation-provider-selection/)
for the full cookie schema, JavaScript examples, and guidance on building a custom branded page.

---

## Invitation error pages

Invitation error pages use **full descriptive names** rather than numeric HTTP status codes because
they represent application-level conditions, not generic HTTP errors.

### `invitation-expired.html`

Served when an invite token has a valid signature but has passed its `exp` claim. AuthProxy validates
the token both when the `/invite/<token>` link is followed (Phase 1) and again at the post-login
exchange (Phase 2), so this page is served whenever an expired token is detected in either phase.
The user should request a fresh invitation.

### `invitation-invalid.html`

Served when an invite token is malformed, carries an invalid signature, or cannot be parsed at all.
Because AuthProxy re-validates the token at the Phase-2 exchange — not only when the link is first
followed — this page is also served if a tampered or forged token is presented at the exchange.

### `invitation-subject-already-exists.html`

Served during Phase 2 (post-login invite exchange) when the exchange endpoint returns HTTP 409 Conflict,
indicating that the authenticated user's subject is already associated with an existing account.

### `invitation-email-mismatch.html`

Served during Phase 2 (post-login invite exchange) when gateway email binding is enabled — the
`Invite.EmailClaim` claim is configured — the invite token was issued for a specific email address — and
the provider supplied another address or explicitly reported `email_verified=false`. This is distinct from
a provider that supplied no address at all.

### `invitation-email-unavailable.html`

Served during Phase 2 when gateway email binding is enabled and the identity provider supplied no
authenticated-session email address. The invite may still target the correct person, but AuthProxy has no
address evidence to compare. Use this page to ask the invitee to choose an account or provider that exposes an
address, rather than telling them their address mismatched.

Email binding is off by default. See [Invitation to Organization](/authproxy/configuration/lobby/invitation-to-organization/) for
configuration and the limits of provider-supplied `email_verified` evidence.

---

## Tenant not found

`tenant-not-found.html` is served when tenant verification is enabled and the platform reports
that the resolved tenant ID does not exist. See [Tenant verification](/authproxy/configuration/tenancy/#tenant-verification)
for how to configure the verification endpoint.

---

## Not authorized

`not-authorized.html` is served when [first-gate authorization](/authproxy/configuration/authorization/) is configured and an
authenticated caller does not satisfy a required claim — a GitHub organization they do not belong to, a
role they do not hold. It is distinct from `403.html`, which answers the *application* refusing a caller it
does recognize; this one is the proxy refusing before the application is reached at all.

If you override it, **keep a way to sign out**. It is the only refusal whose remedy is to come back as
somebody else, and someone who signed in with the wrong account is otherwise stuck looking at a page that
will not change. The built-in page links to `/.cratis/logout?redirect=/`:

```html
<a href="/cratis/logout/?redirect=/">Sign out</a>
```

This is a good page to brand and to make specific — "ask #it-support for access to the planner" is far more
useful to the person reading it than "not authorized".
