Apps are public by default. Authentication can protect any static site, HTML app, handler tree, container service, or selected path.
policies
| policy | verified identity |
|---|---|
required / app-users |
active app-scoped user |
owner |
active owning Kedge account |
google-domains |
exact signed Google Workspace hd claim |
github-orgs |
active GitHub organization membership |
Policy members are ORed. App users may sign in with email, passkey, Google, or GitHub. Workforce identities pass an owner, domain, or organization rule without creating an app-user record.
Policies cover every path by default. Selected paths use exact matches, /**,
or /prefix/**, with optional exceptions.
An email suffix or public organization listing is not sufficient. Invalid patterns, identities, fields, or an empty policy fail the deploy.
request behavior
Protected browser navigations receive a 303 to
/_kedge/auth/login. Protected fetches receive status 401:
{"error":"authentication_required","login_url":"/_kedge/auth/login?next=%2Fadmin"}
Login, denied, and authenticated dynamic responses are private, no-store.
Sessions are scoped to one app and exact hostname. App-user sessions have a
30-day absolute and seven-day idle lifetime; workforce sessions last eight
hours.
HTML apps
<a data-when="!$viewer.verified" href="/_kedge/auth/login">log in</a>
<p data-when="$viewer.verified">Signed in as {{$viewer.name}}</p>
HTML apps expose {{$viewer.id}}, name, verified, and owner.
$verified and $owner restrict individual bindings. Markdown's :account
directive supplies login, identity, and logout controls.
GET /_kedge/auth/me returns {"user":null} or id, name, email, and
provider.
Ingress admission and HTML data ownership are separate. Protecting /admin
does not make its collections owner-only. See the
HTML app reference.
handlers
Handlers receive verified identity as environment variables:
#!/usr/bin/env bash
printf 'Content-Type: application/json\r\n\r\n'
jq -n --arg id "$KEDGE_AUTH_SUBJECT" --arg email "$KEDGE_AUTH_EMAIL" \
'{id: $id, email: $email}'
The complete set is KEDGE_AUTH_SUBJECT, KEDGE_AUTH_EMAIL,
KEDGE_AUTH_PROVIDER, and KEDGE_AUTH_ASSERTION.
services
Container services receive:
X-Kedge-Auth-Subject: <stable identity>
X-Kedge-Auth-Email: person@example.com
X-Kedge-Auth-Provider: passkey | email | kedge | google | github
X-Kedge-Auth-Assertion: <short-lived Ed25519 JWS>
Kedge strips client-supplied X-Kedge-* headers and auth cookies before adding
these headers. Verify the assertion when identity crosses another internal hop.
The verification key is at GET /_kedge/auth/jwks.json.
app-user operations
The owner-authenticated REST API lists, enables, and disables app users, reports configured providers, and revokes app sessions. Disabling a user revokes that user's sessions immediately.
Authentication is ingress authorization, not a role system. Enforce operation-level authorization in HTML binding audiences or application code.
Compose configuration
Authentication is currently declared by the top-level x-kedge.auth property:
x-kedge:
auth: required
A compose.yaml containing only x-kedge does not create a container service.
Use owner for an owner-only app. Use the structured form for paths and
workforce rules:
x-kedge:
auth:
paths: ["/admin/**"]
except: ["/admin/health"]
allow:
owner: true
google-domains: [example.com]
github-orgs: [my-workspace]
paths defaults to ["/**"]; except wins. Top-level auth is the default for
every Compose service. A service-level x-kedge.auth replaces it. See the
Compose reference.