diff --git a/docs/admin/service-accounts.md b/docs/admin/service-accounts.md index f9caf2d7..677ce8fa 100644 --- a/docs/admin/service-accounts.md +++ b/docs/admin/service-accounts.md @@ -52,6 +52,10 @@ Concretely: - Adding `client_credentials` to a client that has no linked SA is rejected — no ownerless M2M clients. - The `authorization_code` / `device_code` / `implicit` paths refuse to issue a token whose client has a `LinkedServiceAccountId`. +::: tip Dual-role BFF backends +A backend-for-frontend that both **brokers user login** (redeeming a native grant like `urn:cocoar:otp` server-side) **and** acts **machine-to-machine** (e.g. minting invite codes via `client_credentials`) needs **two** clients, not one — a login client carrying the native grant **plus** a separate SA-linked client for `client_credentials`. The single shared secret lives on the M2M client; the login client can be public (one secret total) or confidential (client-auth on the redeem). See [Native app integration → server-side BFF](../integrate/native-apps#2-create-the-oauth-client). +::: + ## Creating a Service Account 1. Open `/admin/service-accounts` and click **Create**. diff --git a/docs/integrate/native-apps.md b/docs/integrate/native-apps.md index c3978746..509a22ca 100644 --- a/docs/integrate/native-apps.md +++ b/docs/integrate/native-apps.md @@ -35,6 +35,8 @@ What a realm administrator must configure in Modgud before the app can connect. Granting a native grant on the client sets the matching `gt:urn:cocoar:*` permission. Both the realm flag **and** this per-client permission must be present. +> **Server-side BFF (confidential redeem).** "Public" is the right posture for a true native app that can't keep a secret. A **backend-for-frontend** that redeems the OTP / magic / passkey grant *server-side* (browser never touches Modgud) may instead use a **confidential** client — there is no public-only enforcement on these grants, so a `client_secret` adds client authentication on top of the user's factor. If such a BFF also acts machine-to-machine (e.g. minting [invite codes](../admin/applications#invite-codes-the-invitecode-posture) via `client_credentials`), that is a **separate** client: a single client cannot hold both user-flow and `client_credentials` grants (see [strict grant separation](../admin/service-accounts#strict-grant-separation)). So a dual-role BFF runs **two clients** — a login client carrying `urn:cocoar:otp` (public for a single shared secret, or confidential for client-auth on the redeem) **plus** a separate SA-linked client for `client_credentials`. + ### 3. Passkeys: set the per-client RP-ID and serve an AASA For `urn:cocoar:passkey`, set the client's **WebAuthn RP-ID** to the app's branded apex (e.g. `app.example.com`). If left blank it falls back to the realm's primary domain.