# Ways to sign in

`authentication { }` in `authentication.jadpo` lists the ways your app
lets a caller prove who they are. Jadpo has four, each a block with its
own name, and each decides where the caller's credential travels. A web
app starts with `browser_session`. Add `api_token` for your own mobile or
command-line clients, and `service_key` for other systems:

| Way in | Credential | Who | Use it for |
|---|---|---|---|
| `browser_session` | A cookie the browser keeps (`HttpOnly`, `Secure`, `SameSite=Strict`) | users | Your web app's pages |
| `api_token` | `Authorization: Bearer` header | users | Your own mobile or command-line clients |
| `external_token` | `Authorization: Bearer` JWT from an identity provider | users | An identity provider you already use, such as your company's single sign-on |
| `service_key` | `Authorization: Bearer` header | services | Other systems, such as a partner's server |

Every way signs in a caller of a kind the [principal](the-principal.md)
declares. The principal, declared once in `app.jadpo`, says which stored
record is a user and which is a service. Its `user:` part is what
`browser_session`, `api_token` and `external_token` sign in, and its
`service:` part is what `service_key` signs in.

An app that accepts all four looks like this. Each `config.<name>` is a
setting declared in `config.jadpo`
([Settings](settings-config-jadpo.md)), with
these types:

- `session_signing_key` is a `SigningKey`;
- `browser_origin` and `oidc_issuer` are each a `Url`;
- `oidc_audience` is a `Text`.

```jadpo
// authentication.jadpo
authentication {
    browser_session: {
        cookie: "todo_session"
        signing_key: config.session_signing_key
        origin: config.browser_origin
    }
    api_token: {
        signing_key: config.session_signing_key
    }
    external_token: {
        issuer: config.oidc_issuer
        audience: config.oidc_audience
    }
    service_key: {
        signing_key: config.session_signing_key
    }
}
```

The `signing_key:` lines are only needed with a revocation delay, which
the next section explains. Without one, the smallest setup for a web app
is `browser_session` with its `cookie:`.

A bearer token is never accepted from the URL. The cookie and the
`Authorization` header are never ordinary route input, so a route can't
read them.

## `signing_key:`

By default, sessions, API tokens and service keys are random values that
Jadpo looks up in the database on every request, and nothing is signed.
An app can set a
[`revocation_delay:`](the-application-and-revocation.md#fewer-database-reads-revocation_delay) in
`application { }` to save those reads. With a delay, Jadpo signs what it
read from the caller's record into their session or token and trusts it
for that long, so a user you disable may keep working until the delay
runs out ([How a delayed session is renewed](the-application-and-revocation.md#how-a-delayed-session-is-renewed)).

The signing key is what Jadpo signs that part with. So once you set a
delay, `browser_session`, `api_token` and `service_key` each need a
`signing_key:`. Without a delay the key is never used, and it does no
harm to leave it written. That way you can switch the delay off by
removing its one line, and back on by adding it.

`signing_key:` reads a `SigningKey` setting, which holds a secret
([Signing keys](signing-keys.md)). A key written
in your source is a compile error.

## `browser_session`

A browser session is a cookie your app's pages send with every request.

- `cookie:` names the cookie: 1 to 64 letters, digits, `_` and `-`. The
  session's token travels in it, `HttpOnly`, `Secure` and
  `SameSite=Strict`, for the whole site.
- A second cookie, the same name with `_csrf` after it, holds the
  session's CSRF token. A CSRF token stops another site from making the
  browser send a write on the person's behalf. Your pages read this cookie
  and send its value back in an `X-Jadpo-CSRF` header on every write.
- A session lasts 30 days from when it starts.

`origin:` reads a `Url` setting: the scheme, host and port your app's
pages are served from, such as `https://notes.example`. A write that comes
with the cookie must carry exactly that `Origin` header. Jadpo compares it
with the setting, not with the request's `Host`, so a proxy in front of
your server changes nothing. Leave `origin:` out and a write's `Origin`
must name the server its `Host` header gives.

[Magic links](magic-links.md) and [passkeys](passkeys.md), the two ways a
person signs in to start a browser session, need `origin:`.

## `api_token` and `service_key`

An API token signs in a user, so the app's principal must declare
`user:`. A service key signs in a service, so the principal must declare
`service:`. Inside `authentication { }`, each block takes only
`signing_key:`. Without a revocation delay there is nothing to set, so
you write `api_token: {}` and `service_key: {}`.

Each travels as `Authorization: Bearer <value>`. An API token is
`jadpo_token_` and 64 hexadecimal digits, and a service key `jadpo_key_`
and 64. The prefix tells Jadpo which it was sent, and lets secret scanners
find one that leaks. Both are 32 random bytes. Jadpo keeps only their
BLAKE3 digests, one-way fingerprints that can't be turned back into the
credential, as it does for a session's cookie.

Your app hands out API tokens and service keys from its own actions, the
operations that read and change its data
([Functions and actions](functions-and-actions.md)).
See [API tokens](starting-and-ending-a-session.md#api-tokens) and [Service credentials](service-credentials.md).

## `external_token`

Use it when people already sign in with an identity provider, and your
app should accept the JWTs it issues. A JWT is a signed token that names
the person, its subject.

`issuer:` reads a `Url` setting and `audience:` a `Text` setting. Jadpo
compares each exactly with what the token says. It accepts EdDSA, RS256
and ES256 JWTs, using the JWT support in the
[standard library](standard-library.md).

A token is accepted only for a subject your app already has a user for.
A subject no user has is refused with `sign_in_refused` (403).

Only the subject is used. Every request reads that user's current record,
which must still meet the principal's `active:` condition and be a record
the entity's lifecycle shows. Profile and permission claims from the
provider are never stored as the user's identity, and never become
policy grants.

To check a token's signature, Jadpo fetches two documents from the
provider itself: its discovery document, a JSON file at a fixed address
under the issuer that says where its keys are, and its key set (JWKS),
the public keys it signs tokens with. An app that doesn't declare
`external_token` makes no such requests.

An invalid token is 401. Invalid provider or configuration data is
`authentication_misconfigured` (500), and a provider Jadpo can't reach is
`authentication_unavailable` (503). No response shows the provider's
content or a credential.

The limits on these fetches are at the end of this topic.

## Exchanging a service key for a token

With a revocation delay and service keys, Jadpo generates
`POST /auth/exchange`, which trades a key for a short-lived token. A
service sends its key as `Authorization: Bearer` and nothing else, and
gets `{ "token": ..., "expires_at": ... }`.

The token is `jadpo_service_token_` and a signed part. It lasts as long
as the revocation delay, or until the key expires if that is sooner. It signs the service in on every
route its key would. Jadpo reads the key together with the service's
record when it exchanges it, so a disabled service is refused at the
exchange.

You can't write a route of your own at `POST /auth/exchange`: one is the
compile error `route.path_overlap`.

## Where Jadpo fetches the provider's keys

For `external_token`, Jadpo fetches only from addresses you configured:

- The configured issuer and the key address it names must use HTTPS,
  with no redirects, and only addresses on the public internet.
- The discovery document must name exactly the configured issuer,
  including its trailing slash. Its `jwks_uri`, the address of the key
  set, may name another public HTTPS host.
- The token never supplies an address to fetch.
- Discovery adds `/.well-known/openid-configuration` after the issuer's
  path, removing a final slash for that request only
  ([OpenID Connect Discovery §§3, 4.1, 4.3](https://openid.net/specs/openid-connect-discovery-1_0.html)).
- A native build checks the addresses DNS actually returns, and refuses
  any that isn't public. On Cloudflare Workers, Jadpo sends the fetches
  with Workers' `global_fetch_strictly_public` flag, which keeps them to
  the public internet.

## Limits on the provider's keys

Each running instance of the app keeps one cache of the provider's keys:

- at most 64 keys, a 64 KiB discovery document and a 256 KiB key set;
- 3 seconds for each fetch;
- keys kept for 5 minutes, and at most one refresh every 30 seconds. A
  refresh counts even when the request that started it is cancelled.

The route's own time limit also cancels discovery and key requests.
Encryption keys in the set are ignored, and signing keys must meet the
standard library's strict `Jwk` rules.

Not built yet: with `external_token`, a principal `subject:` field made
of several columns, or of a structured value, is
`toolchain.not_built_yet`.
