# OAuth grants

**Not built.** This page is the design; Jadpo doesn't do this yet. See [What is built](/docs/external-services-built.md).

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.

Signing in proves to your app who a person is. A grant goes the other way:
your app acts at another service, 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.

```jadpo
// app.jadpo
calendar: {
    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.jadpo
attempt 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
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. 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:

1. `POST /connections/<name>/start`, called by the signed-in caller,
   returns the provider's consent address. It needs a signed-in caller, so
   `authentication: RouteAuthentication.none` isn't available on it. It
   stores the OAuth `state` on the server, against the caller's browser
   session or API token.
2. The provider sends the person back to the generated
   `GET /connections/<name>/callback`. It finds the `state`, so a consent
   can complete only once, for the caller who started it. 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 the app's own API-token clients connect
the same way as browsers. Both routes appear in `jadpo routes`, OpenAPI and
the API record, marked generated
([Generated routes](/docs/generated-routes.md)).

## 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 } }`,
a `SigningKey` setting. That key is a secret, rotated as a `signing_key:`
is: the current value encrypts, and its `_PREVIOUS` value is still read
([Signing keys](/docs/signing-keys.md)).
`encryption_key:` is one of the places in `application { }` a secret may be
read ([Secrets](/docs/secrets.md)). No code can read a
grant.

## Calling with a grant

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. The caller of the
route need not be the owner: a scheduled job acting for an organisation
names the organisation. `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](/docs/policy.md) decides who may connect for an owner. 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. Grants bring in no new kind of
permission, and the start route checks this before it answers.

## 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](/docs/erasure.md))
deletes and revokes its grants.

## In development and tests

Calls are faked by default. `jadpo dev grants add <connection> --owner <id>`,
not built yet, 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, their routes, `on_behalf_of:` and `jadpo dev grants add` aren't
> built yet, and nor is acting as an OAuth provider
> ([What is built](/docs/external-services-built.md)).
