The Access Token

The one short token, for every audience: a JWT the account service signs, naming whose it is, for whom, for what, and until when.

  • Proves: an account's identity, and who's acting for it (the machine, or the person in the browser), to one audience, with what it may do there.
  • Made by: the account service, from a machine assertion, or from a person's sign-in in the browser for a visitor's token. It signs with a key of its own, a secret it holds alone.
  • Held by: whatever asked for it, in memory only, never written to disk. On a machine the accounts plugin gets one and hands it to the plugin that asked, through a pipe, never to a terminal: lnk tunnel for the relay, lnk bucket for the share API.
  • Travels: as Authorization: Bearer, over https://, or to a relay on this machine. lnk tunnel sends the relay its token in the connection's Hello too.
  • Lasts: as long as its signer says, and no longer than its checker's longest life. Local Link's account service signs each for 5 minutes. The relay takes one of at most its spec's [tokens] max_life, 15 minutes unless its operator says otherwise (Relay Spec).
  • Revoked: never one by one: its short life is its end. A machine signed out gets no more, and link-relay admin user block <user> refuses a user's at once on that relay.
  • May: what its grants say, at its audience, and nothing else.

Its form

A compact JWS: <header>.<claims>.<signature>, each base64url.

The header and iss

The header holds alg EdDSA, typ at+jwt, kid the signing key's id, and nothing else. iss is the account service's origin, as https://accounts.example.com.

sub

Who's acting, as RFC 7519 means it, <kind>:<id>: machine:<its id> for a machine's, agent:<its id> for a machine's agent's, as itself, and person:<uuid> for a visitor's, today their account's own uuid. The kind is all before the first :; the id after it is opaque and may hold : and /. The kinds, the spec's PrincipalKind, are machine, person, agent, service and api-key.

acc and aud

acc is the account it acts in, acct-<32 hex>: whose resources its grants reach. aud is one audience: a relay's domain, as local-link.xyz, or a service's origin.

act and scope

act, optional, is the service acting for sub, as {"sub": "service:<its name>"} (RFC 8693's actor); no token has one yet. scope is its grants, separated by spaces; none for a token that only says who it is.

usr and email

usr is the account's username when it was made, for the relay's addresses alone. email is the account's email, only in a visitor's token and only when the account service has verified it.

nonce, iat and exp

nonce is only in a visitor's token: the relay's state for the sign-in it answers, so it can't be carried to another. iat and exp are when it was made, and when it ends, in Unix seconds.

How it's checked

What a checker refuses, and the keys it checks with.

The header first

A checker reads the header before anything else, and refuses a token whose alg isn't EdDSA (none among them), whose typ isn't at+jwt, whose header holds anything more, or whose kid it doesn't hold.

Then the claims

It then checks the signature with that key, iss the account service it trusts, aud its own, a grant of what it's asked to do (Scopes), exp in the future and at most its longest life away, and exp no further than that from iat. It allows no difference of clocks.

A claim it doesn't know

A claim it doesn't know, or one missing, is refused: a token as they were made before grants, its account in sub, with mch and one scope, among them.

The service's keys

Checkers hold the account service's public keys, each with its id, and never ask the service about a token: a service that's down or slow costs them nothing. The relay is given them as --accounts-key.

Replacing a key

To replace its key, the account service publishes the new one beside the old, signs with the new once every checker holds it, and drops the old one its longest life later.

Scopes

A machine asks for a token of one scope, in its assertion; the token holds that scope's grants in scope, and what checks it reads only the grants.

tunnel

Grants tunnel.open@tunnel:*, for a relay: a machine opens tunnels in its account's namespace there, serves them and lists them.

share

Grants share.make@share:*, for a relay: a machine makes, lists and ends its account's share links there.

visit

Grants tunnel.visit, for a relay: a person, signed in in the browser, visits that relay's tunnels whose allow names their account or email.

api

Grants machine.read machine.admin agent.admin, for the account service: a machine calls its API as that machine, to list and sign out the account's machines, add a box's machine key, and report its agents.

identify

Grants none, for any service, by its origin: a machine says who it is to that service alone, which checks the token itself.

agent

Grants what the machine names for one of its agents, for any service by its host but the account service and a relay. The assertion names the agent by its id (agent) and its grants there (grants), at most 32, such as team.member@team:<id> for each team it's on, and the token's sub is agent:<its id>. lnk auth token --audience <host> --agent-id <id> --grants <grants> asks for one, for the program that adds it to the agent's calls, never the agent itself.

Whose agent

An agent's token says what its machine asserts, nothing more: its sub is the agent the asking account names, which the account service holds only to the machine's own last report (lnk remote), and a machine writes that report itself. So sub may name an id another account's agent has, and a checker must hold the agent, and each resource the token grants, to acc before it trusts either. Its grants are inside the account alone: none of another's (acct-<id>/..., */...).

Grants

A grant is <action> or <action>@<resource>.

An action

<kind>.<verb> (tunnel.open), <kind>.* for every verb of a kind, or * for every action.

A resource

<kind>:<id> (tunnel:web), or a pattern ending in * for every one it begins (tunnel:*, tunnel:ci-*), or *; a grant without one reaches every resource of the account.

allows(action, resource)

Whatever checks a token asks one question, allows(action, resource): whether a grant names that action on that resource. It looks for the grant of the action it serves and skips every other, an action it doesn't know among them, so an action can be added to tokens without a relay that's older refusing them.

The kinds it takes

Each checker also takes only the kinds of sub it serves, so a broad grant to another kind is never read as one of them.

What the relay asks

The relay takes a machine's token, asking tunnel.open on tunnel:* to open, serve and list tunnels and share.make on share:* for share links, and a person's for a visitor, asking tunnel.visit on tunnel:*: every one of the account's, so only a grant of them all lets it in.

What the account service asks

The account service's API takes a machine's, asking what each route names in its spec: on the token's own machine (machine:<its id>) to say who it is or sign it out, on the machine a route's path names, its id checked first, and on every machine (machine:*) or agent (agent:*) of the account for the others.

What an identify checker asks

A service checking identify tokens takes a machine's and asks no grant: sub and acc say who.

The cases

The account service's spec holds the cases allows answers (Grants, x-allows), which its checkers in Rust (link_api::jwt) and the account service's own run.

Not built yet

The relay takes a token of at most 15 minutes, not its spec's [tokens] max_life.