# The application and revocation

A person can be disabled, or a session revoked, while they still hold a
valid cookie or token. By default, Jadpo looks up the caller's record on
every request, so revoking a session or disabling a user takes effect on
the very next request. You don't write anything for that. Sessions and
tokens are random values Jadpo looks up in the database.

`application { }` in `app.jadpo` can change this with one setting,
`revocation_delay:`, and also holds the app's `display_name:`.

## Fewer database reads: `revocation_delay:`

Use it when looking up the caller's record on every request costs too
much, and a short window in which a disabled user can still act is
acceptable.

```jadpo
application {
    revocation_delay: 5m
}
```

With `revocation_delay: 5m`, Jadpo signs what it learned from the caller's
record into their session or token and trusts that for up to five minutes,
without reading the database. A user you disable may keep working for up
to five minutes. After that, the next request reads the record again.

Three kinds of request always read the record, whatever the delay:

- a request with a token from an outside identity provider
  (`external_token`, see [Ways to sign in](ways-to-sign-in.md));
- a request that sends a service key itself, rather than a token exchanged
  for one;
- a request to a route marked `authentication: RouteAuthentication.fresh`
  ([Routes: signed in by default](routes-signed-in-by-default.md)).

This is the only revocation setting. Leave it out for the strict
behaviour.

## How a delayed session is renewed

A credential is issued the same way with or without a delay. A session's
cookie and an API token are 32 random bytes, and Jadpo keeps only a
verifier of them, a one-way digest that can't be turned back into the
credential.

The first request with a new credential reads the record. Its response
renews the credential with a part Jadpo signs, holding what that read
found and when it stops counting: the delay after the read, or the
session's end if that is sooner. Every request after that signs in from
the signed part alone. When the part expires, the next request reads the
record again and renews it the same way.

- A browser session's renewed cookie comes back in `Set-Cookie`, which the
  browser keeps by itself.
- An API token's comes back in an `X-Jadpo-Token` response header. Your
  client keeps it and sends it from then on. A client that ignores it still
  signs in, but reads the record on every request.
- A token a service gets from `POST /auth/exchange` (see
  [Exchanging a service key for a token](ways-to-sign-in.md#exchanging-a-service-key-for-a-token))
  is signed when it is given and never renewed. When it expires, the
  service exchanges its key again.

## Signing keys

With a delay, each way of signing in needs a `signing_key:`, which reads a
`SigningKey` setting from your configuration
([Signing keys](signing-keys.md)). New parts are
signed with the setting's current value, and parts signed with its
`_PREVIOUS` value still count, so you can rotate a key without signing
anyone out.

A signed part the key doesn't open, or one that has expired, is never
refused for that. The request reads the record, as it would with no delay.

## The app's name: `display_name:`

`display_name:` is your app's name as people see it, such as `"Notes"`.
Jadpo's own emails name your app by it, so it is required once you switch
on magic links or another
[built-in email](built-in-services.md).

## A whole app with a delay

The settings, the delay, a user and a service, and each way of signing in
with its key:

```jadpo
config {
    browser_origin: Url
    session_signing_key: SigningKey
}
```

```jadpo
use journal/entities { Member, Partner }
use journal/types { MemberStatus, PartnerStatus }

application {
    revocation_delay: 5m
}

principal {
    user: {
        subject: Member.handle
        active: Member.status == MemberStatus.active
    }
    service: {
        subject: Partner.name
        active: Partner.status == PartnerStatus.active
    }
}
```

```jadpo
authentication {
    browser_session: {
        cookie: "notes_session"
        signing_key: config.session_signing_key
        origin: config.browser_origin
    }
    service_key: {
        signing_key: config.session_signing_key
    }
}
```

A partner signs in with its key, which always reads the record, or with
the token the exchange gave it for that key, which reads nothing until it
expires. A `fresh` route reads the record either way:

```jadpo
use journal/types { PartnerView }
use journal/queries { this_partner }

/// What a partner reads, signed in with its key or a token the exchange gave.
routes Partners {
    prefix: /partners

    /// Signs in without a read while the token's signed part lasts.
    GET /me {
        output: PartnerView
        run: this_partner(current_principal.service)
    }

    /// Reads the partner's record now, whatever the token says.
    GET /me/fresh {
        authentication: RouteAuthentication.fresh
        output: PartnerView
        run: this_partner(current_principal.service)
    }
}
```

The API record, the file `jadpo api update` writes to list each API's
routes ([The public API record](the-public-api-record.md)), lists
the exchange Jadpo answers, as `jadpo routes` and OpenAPI do
([Generated routes](generated-routes.md)):

```jadpo
// Generated by Jadpo from the routes of API main; update it with `jadpo api update`, never by hand.
record api 1

api main
    authentication browser_session
    authentication service_key

POST /auth/exchange
    generated exchange
    caller service
    output ExchangedToken
    status 200

GET /openapi.json
    generated openapi
    status 200

GET /partners/me
    caller service
    output PartnerView
    status 200
    fails partner_not_found

GET /partners/me/fresh
    caller service
    output PartnerView
    status 200
    fails partner_not_found

output ExchangedToken
    expires_at: Instant
    token: ServiceToken

output PartnerView
    name: Text { min_length: 3, max_length: 40 }

failure partner_not_found
    status 404
```
