> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Choosing an auth mode

> Hosted or federated — the one decision to make before wiring the front end.

Contacts get into your product one of two ways. You pick per environment, so you
can evaluate one in test while production runs the other.

<CardGroup cols={2}>
  <Card title="Hosted" icon="lock" href="/en/customer-auth/hosted">
    **UserKit owns the account.** Sign-up, sign-in, email verification and password
    recovery are endpoints you call. You build the forms; the rules are ours.
  </Card>

  <Card title="Federated" icon="signature" href="/en/customer-auth/federated">
    **You already have auth.** Your server vouches for a user with an HMAC over
    their id, and UserKit trusts the claim.
  </Card>
</CardGroup>

## Which one

|                             | Hosted                             | Federated                |
| --------------------------- | ---------------------------------- | ------------------------ |
| Who stores the password     | UserKit                            | You                      |
| Who owns the sign-in screen | You (our endpoints)                | You (entirely)           |
| What proves identity        | A password, or a link in the inbox | An HMAC from your server |
| Good when                   | UserKit is the account system      | You have users already   |

If you are starting from nothing, hosted. If your product already has a users
table, federated — you are not migrating anyone.

## Setting it

```bash theme={null}
curl -s -X PATCH $API/v1/organization/environments/{id} \
  -H "Authorization: Bearer $UK_SESSION" \
  -H "X-Organization-Id: org_4b1e…" \
  -H 'Content-Type: application/json' \
  -d '{ "auth_mode": "federated" }'
```

Requires `organization:update`. Both environments start as `hosted`.

## Reading it from a login screen

A sign-in screen has to know which mode it is in before it can render itself,
and taking that as configuration means flipping the mode does nothing until
somebody redeploys. Ask instead:

```bash theme={null}
curl -s $API/v1/config/uk_pk_live_…
```

```json 200 theme={null}
{
  "environment": "live",
  "auth_mode": "hosted",
  "providers": ["google"]
}
```

Public and cacheable — no key beyond the publishable one, no `Origin` check, so
it works from a server rendering the page. `providers` lists what a contact
could actually sign in with, so turning Google on in the panel lights the button
up on its own. See [Social sign-in](/en/customer-auth/social).

## The modes refuse each other

The endpoints of the mode an environment is *not* in answer `409` rather than
quietly becoming a second sign-in path:

```json /v1/contact-auth/* on a federated environment theme={null}
{
  "error": {
    "code": "environment_not_hosted",
    "message": "this environment is in federated mode; identify contacts via boot instead"
  }
}
```

```json /v1/boot with external_id, on a hosted environment theme={null}
{
  "error": {
    "code": "environment_not_federated",
    "message": "this environment is in hosted mode; switch it to federated to identify contacts via boot"
  }
}
```

Two sign-ins into one account system is how the weaker one becomes the way in.

<Note>
  Anonymous boot — a publishable key and an `anonymous_id`, no `external_id` — works
  in **both** modes. Visitor tracking and attribution are not an authentication
  question, so they never hit the mode check.
</Note>

## What both modes share

Whichever you pick, the same things hold.

**Contacts are contacts.** Same rows, same identity edges, same attribution, same
merges. The mode decides how a contact gets *proven*, not what one is.

**Only a link proves an email.** In both modes, `email_verified` flips when a
message arriving in the inbox is clicked — never because an email was passed to an
API.

**Sessions carry `verified`.** A proven identity mints a verified
[contact session](/en/concepts/credentials#contact-sessions); an unproven one
mints a session marked unverified and barred from anything another person's data
could leak through.

**Responses do not enumerate.** Sign-up, magic link, resend and forgot-password all
answer `202` unconditionally, and sign-in answers one uniform `401`, in both modes.

**The origin list is the gate.** Every publishable-key endpoint that writes
anything — boot, sign-up, sign-in, magic link, social — checks the
[allowed-origins list](/en/guides/api-keys#publishable-keys) before anything else.
The two public **reads** addressed by key, `/v1/config` and `/v1/jwks`, do not:
they mint nothing and mail nobody, and gating them would only stop a server
rendering your login page, which sends no `Origin` at all.

**Your backend verifies the same way.** Whichever mode the environment is in, a
contact session buys a short-lived JWT your API checks offline. See
[Session tokens](/en/customer-auth/session-tokens).
