Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Identity

Identity

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_key is a SigningKey;
  • browser_origin and oidc_issuer are each a Url;
  • oidc_audience is a Text.
example
// 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, 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 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-configuration after 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_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.

Markdown for agents: /docs/ways-to-sign-in.md.Built from Jadpo main at a707efb.