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

Acting for a user at another service: OAuth grants

Signing in proves to your app who a person is. An OAuth grant goes the other way: your app acts at another service, such as a user's Google Calendar, an organisation's Xero or a Slack workspace, with that owner's consent. It isn't a way to sign in. Your app acting as an OAuth provider, for other apps to sign in with, is a third, separate thing.

The connection and its calls are in Integrations. This topic covers what touches identity. A connection's credential is an OAuthGrant:

text
// app.jadpocalendar: {    package: GoogleCalendar.v3    domains: [scheduling]    credential: OAuthGrant {        owner: User        client_id: config.google_client_id        client_secret: config.google_client_secret        scopes: [...]        return_to: "/settings/integrations"    }}
  • A grant is owned by one of the app's records: owner: is the principal's user entity or any other entity (Organisation). The grant belongs to that record, not to a session, so it outlives every session and is used from subscribers and schedules, which run with no caller.
  • Connecting is endpoints, not pages. POST /connections/<name>/start is a generated route that needs a signed-in caller (authentication: RouteAuthentication.none is not available on it) and answers with the provider's consent address. It keeps the state on the server against the caller's browser session or API token. The provider sends the browser back to the generated GET /connections/<name>/callback, which finds the state (a consent can complete only once, for the caller who started it), exchanges the code with PKCE, stores the grant and redirects to the frontend's return_to, a path on the app's own site, with success or failure. The callback needs no cookie, so your own API-token clients connect exactly as browsers do. Both appear in jadpo routes, OpenAPI and the API record, marked generated (Generated routes).
  • Who may connect for an owner is Policy's. Connecting and disconnecting need the update grant on the owner's record. So a user connects their own account, and an organisation's grant needs whatever lets the caller update the organisation. There is no new kind of permission for it. The start route checks it before answering.
  • Grants are held by Jadpo, like sessions, in a jadpo_grants system table in the owner entity's store, so grants need a database. Their tokens are encrypted with application { grants: { encryption_key: config.grant_encryption_key } }, a SigningKey setting. It rotates as a signing_key: does: the current value encrypts, and its _PREVIOUS value is still read (Signing keys). Code can never read the tokens, and Jadpo refreshes them before they expire. encryption_key: is one of the places in application { } a secret may be read (Secrets).
  • Every call names whose grant it uses, on_behalf_of: owner_id, an id of the owner's entity that the action has loaded or been given. grant_missing and grant_revoked are failures your app maps to its own (Integrations). The caller of the route need not be the owner: a scheduled job acting for an organisation names the organisation.
  • Disconnecting is the generated POST /connections/<name>/disconnect and its operation, revoking at the provider when the package declares how. Erasing a person or a record (Personal data) deletes and revokes its grants. A revoked grant gives the GrantRevoked event, so your app can ask the owner to reconnect.
  • In development and tests, jadpo dev grants add <connection> --owner <id> makes a fake grant, and tests declare grants as fixtures.

Not built yet: OAuth grants, their routes and on_behalf_of:. Acting as an OAuth provider isn't built either.

Markdown for agents: /docs/acting-for-a-user-at-another-service-oauth-grants.md.Built from Jadpo main at a707efb.