Acting for each user: OAuth grants
On this page
Use a grant when your app acts at another service for each of its users or organisations: a user's Google Calendar, an organisation's Xero, a Slack workspace install. Each owner connects their own account, and your app calls the service with that owner's permission.
// app.jadpocalendar: { package: GoogleCalendar.v3 domains: [scheduling] credential: OAuthGrant { owner: User client_id: config.google_client_id client_secret: config.google_client_secret scopes: ["calendar.events"] return_to: "/settings/integrations" }}// domains/scheduling/actions/add_to_calendar.jadpoattempt calendar.events.insert(event, on_behalf_of: booking.organiser_id) { grant_missing: CalendarNotConnected grant_revoked: CalendarNotConnected}
The grant is the connection's credential
A connection's credential may be a grant owned by one of the app's
records. The owner is User or any entity, such as Organisation. The
provider's OAuth facts (its authorize, token and revoke addresses) are
oauth: in the package's provider.jadpo, and each operation declares
the scope it needs (scope: "calendar.events"). Nothing is specific to
one provider, and the connection's name is the app's.
Connecting
Connecting is two generated endpoints, not pages:
POST /connections/<name>/start, called by the signed-in caller, returns the provider's consent address. It stores the OAuthstateon the server, against the caller's session or API token.- The provider sends the person back to the generated
GET /connections/<name>/callback. It finds thestate, exchanges the code with PKCE, stores the grant, and redirects to the frontend'sreturn_towith success or failure.
The callback needs no cookie, so the app's own API-token clients connect the same way. Acting for a user at another service: OAuth grants covers how this touches sign-in.
Where grants are kept
The runtime holds grants, as it holds 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 } }.
That key is a secret, read and rotated as signing keys are, with its
previous key kept (Signing keys).
No code can read a grant.
Calling with a grant
Every call names whose grant it uses: on_behalf_of: owner_id.
grant_missing and grant_revoked are failures the app maps, as the
example maps both to CalendarNotConnected.
Tokens are refreshed for you: renewed before they expire, once when
several calls race, and a rotated refresh token kept. A revoked grant
gives grant_revoked and a GrantRevoked event, so the app can ask the
owner to connect again.
Scopes are checked when you compile: calling an operation whose scope the connection doesn't request is an error.
Who may connect
Policy decides who may connect for an owner. Connecting
and disconnecting need the update grant on the owner's record, so
grants bring in no new kind of permission.
Disconnecting
The generated POST /connections/<name>/disconnect, and its operation,
disconnect a grant, revoking it at the provider when the package declares
how. Erasing a person or a record (Erasure)
deletes and revokes its grants.
In development and tests
Calls are faked by default. jadpo dev grants add <connection> --owner <id>
makes a fake grant, and tests declare grants as fixtures.
Later
Each of these comes when an app needs it: several accounts per owner, scopes added after connecting, provider notifications per grant, and GitHub App installation tokens (a credential scheme in the runtime).
OAuth grants aren't built yet (What is built).
Markdown for agents: /docs/acting-for-each-user-oauth-grants.md.Built from Jadpo main at a707efb.