Compare commits

...

1 Commits

Author SHA1 Message Date
Omar Mihilmy c8c4547e69 docs(claude-in-office): clarify entra_scope — multi-scope support, looser validation
The add-in now accepts entra_scope as a comma/whitespace-separated list and
no longer requires a URI-shaped value (Entra validates scope syntax, not the
build script). Update the docs and the build-manifest validation to match:

- build-manifest.mjs: entra_scope pattern relaxed to any non-blank string;
  the graph_client_id pairing check is unchanged.
- manifest.md: document the multi-scope list syntax and the same-resource
  constraint; clarify the build script enforces the pairing, not the format.
- bootstrap.md: note scp is a space-delimited list with multiple scopes.
- debug.md: add a Silent SSO / Entra token failures troubleshooting section
  (AADSTS50194, entra_scope-without-graph_client_id, silent-then-popup).
2026-05-14 10:55:41 -04:00
4 changed files with 40 additions and 8 deletions
@@ -141,8 +141,9 @@ With `entra_sso=1`, validate the JWT before trusting it:
If you set `entra_scope` in the [manifest](manifest.md#entra-sso), the Bearer
is an **access token**, not an ID token. Validate `aud` = your API's
Application ID URI (`api://<guid>`, not the client GUID) and check `scp`
contains the scope you defined. `iss`, `exp`, `oid`, and signature verification
are the same.
contains the scope(s) you defined `scp` is a space-delimited list when
`entra_scope` names more than one. `iss`, `exp`, `oid`, and signature
verification are the same.
Signature verification needs Microsoft's JWKS
(`https://login.microsoftonline.com/<TENANT_ID>/discovery/v2.0/keys`). Use a
@@ -139,6 +139,25 @@ broker maps any unclassifiable close to that).
---
## Silent SSO / Entra token failures
- **`AADSTS50194: …not configured as a multi-tenant application` /
`Use a tenant-specific endpoint`** — your `graph_client_id` (or the
`entra_scope` resource app) is a single-tenant app, and the add-in build is
old enough to still request tokens against the `/common` authority. Newer
builds resolve a tenant-specific authority automatically when
`graph_client_id` is set. Fix: have users update to the latest add-in
version. There is no manifest workaround on an old build.
- **`entra_scope requires graph_client_id`** — `entra_scope` was set without
`graph_client_id`. Custom-scope access tokens must be issued by your own
Entra app, not the default; set both. The build script also rejects this
pairing.
- **Silent SSO fails, then an interactive popup works** — expected on first
run before a service principal exists in the tenant. Once admin consent is
granted (see above) the silent path succeeds.
---
## Opening browser devtools on the add-in
When you need the WebView's console — JS errors, network tab, the add-in's
@@ -82,10 +82,20 @@ it, then grant admin consent for the tenant. In the app manifest, set
leave it unset and you get v1.0 tokens, which your validator may reject.
`/.default` (requests all consented scopes) also works.
`entra_scope` requires `graph_client_id` — the build script enforces this. Both
are manifest-only: the add-in needs them to initialize NAA *before* it can read
extension attrs or call your bootstrap endpoint, so neither can arrive through
those layers. Leave `entra_scope` unset and the ID token is sent.
**Multiple scopes.** `entra_scope` accepts a comma- or whitespace-separated
list — `entra_scope=api://<guid>/use_llm,api://<guid>/admin`. All scopes must
target the **same resource**: one access token has one `aud`, so MSAL cannot
mint a token spanning two APIs (`api://torii/x,api://other/y` will fail or
silently honor only one). The Bearer's `scp` claim is the space-joined list.
If you need every consented scope, prefer `/.default` over enumerating them.
`entra_scope` requires `graph_client_id` — the build script enforces *that
pairing* but not the scope string itself: any non-blank value is accepted and
Entra validates the syntax at sign-in (a malformed scope surfaces as an
`AADSTS` error, not a build failure). Both keys are manifest-only: the add-in
needs them to initialize NAA *before* it can read extension attrs or call your
bootstrap endpoint, so neither can arrive through those layers. Leave
`entra_scope` unset and the ID token is sent.
## Bootstrap endpoint
@@ -62,8 +62,10 @@ const KEYS = {
hint: "your Entra app registration's Application (client) ID — overrides the default multi-tenant app",
},
entra_scope: {
pattern: /^(api|https):\/\/\S+\/[\w.-]+$/i,
hint: "scope URI for your Entra-protected API, e.g. api://<your-app-guid>/.default — requires graph_client_id",
// Any non-blank string — Entra validates scope syntax, not us. May be a comma- or
// whitespace-separated list (the add-in splits it); requires graph_client_id (enforced below).
pattern: /\S/,
hint: "scope(s) for your Entra-protected API, e.g. api://<your-app-guid>/.default — comma/space-separated list allowed, requires graph_client_id",
},
allow_1p: {
pattern: /^[01]$/,