Reference

Policies in karu

Say who may sign in to your site, in karu, a small policy language people can read. wellknown.id checks your rules at every sign-in it bridges.

karu is a policy language: embeddable, deny-overrides and default-deny, matching patterns over JSON, with a syntax like Polar's. It interoperates with Cedar (karu import turns Cedar policies into karu). wellknown.id checks your rules at every sign-in it bridges, after the key proof and before it issues a code: at /auth, in FedCM's window, and when a wallet signs in for another device.

Writing one

Rules only ever narrow: each deny that matches turns the sign-in down, and wellknown.id supplies the allow. An allow rule in your policy changes nothing.

deny not_p256 if principal.key_type != "P-256";
deny no_popin if context.mode == "popin";

not_p256 takes only P-256 keys, the kind a phone can keep in its secure hardware. no_popin doesn't let the sign-in run in a frame inside your page. Put it in your config entry as a JSON string (or at the top level, for every entry on your domain):

{
  "redirect_uris": ["https://your.site/"],
  "policy": "deny not_p256 if principal.key_type != \"P-256\";\ndeny no_popin if context.mode == \"popin\";"
}

A turned-down sign-in reaches your page as wellknown-id-login-failure, with an error naming the rules: access_denied: denied by policy: not_p256. A policy that doesn't compile, or has an import, or is over 32 KiB, refuses every sign-in as invalid_client, so try a change before you publish it.

What rules can look at

Every sign-in is checked against one document:

{
  "principal": { "id": "did:key:zDn…", "key_type": "P-256" },
  "action": "login",
  "resource": { "client_id": "acme.example", "domain": "acme.example", "redirect_uri": "https://acme.example/callback", "scopes": ["openid"] },
  "context": { "mode": "popup", "config_source": "http", "auth_age_seconds": 0 }
}
Field
principal.idthe did:key your site will get as sub
principal.key_typeP-256 or Ed25519
principal.actor, context.delegatedwhere someone acts for the person (delegation): the actor's did:key at your site, and true. deny no_delegation if context.delegated == true; refuses them
resource.client_id, resource.domainyour domain (both, so a policy may use either)
resource.redirect_uri, resource.scopesthe request's
context.modenavigate, popup, popin, fedcm or wallet. popup and navigate come from your page's own request, so they're your page's word
context.config_sourcewhere your config came from: dns or http
context.auth_age_secondsalways 0: every sign-in proves the key afresh

Where it doesn't apply

A self-issued token your site takes itself never reaches wellknown.id, so wellknown.id checks nothing for it: your site enforces its own rules. wellknown.id's own rules are checked first, and a sign-in they refuse is reported as wellknown.id.<rule>.

Coming

User-authored policies, written by people about what they'll share, with tools that guide them: planned.