Ways to sign in
On this page
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
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), with
these types:
session_signing_keyis aSigningKey;browser_originandoidc_issuerare each aUrl;oidc_audienceis aText.
// authentication.jadpoauthentication { 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: 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 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). 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,SecureandSameSite=Strict, for the whole site.- A second cookie, the same name with
_csrfafter 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 anX-Jadpo-CSRFheader 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 and passkeys, 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). See API tokens and Service credentials.
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.
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-configurationafter the issuer's path, removing a final slash for that request only (OpenID Connect Discovery §§3, 4.1, 4.3). - 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_publicflag, 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.
Markdown for agents: /docs/ways-to-sign-in.md.Built from Jadpo main at a707efb.