Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89f6b3c609 | |||
| a29759e365 | |||
| b9ccfc9158 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "claude-for-msft-365-install",
|
||||
"description": "Provision direct cloud access (Vertex AI, Bedrock, or LLM gateway) for the Claude Office add-in. Generates the customized add-in manifest, walks through Azure admin consent, and writes per-user config via Microsoft Graph extension attributes.",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"author": {
|
||||
"name": "Anthropic",
|
||||
"email": "support@anthropic.com"
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Register your own Entra app
|
||||
|
||||
Several manifest configurations require an Entra (Azure AD) app registration in
|
||||
**your** tenant rather than Anthropic's default multi-tenant app — because the
|
||||
token's `aud` must match a resource you control, or because your tenant is in a
|
||||
sovereign cloud where Anthropic's app doesn't exist. This page is the single
|
||||
set of registration steps; the per-feature docs link here and tell you which
|
||||
row of the permissions table applies.
|
||||
|
||||
You need this when setting any of:
|
||||
|
||||
| Manifest key | Why your own app |
|
||||
|---|---|
|
||||
| `graph_client_id` (Outlook) | Graph permissions are consented against your app, not Anthropic's |
|
||||
| `entra_scope` | Access token must be audienced to *your* API resource |
|
||||
| `gateway_auth_source=entra` | Gateway validates a token audienced to your API |
|
||||
| `graph_cloud` ≠ `global` | Anthropic's app exists only in the commercial cloud |
|
||||
|
||||
## Register the app
|
||||
|
||||
In [Entra admin center](https://entra.microsoft.com) → *App registrations* →
|
||||
*New registration*. Single-tenant. The simplest topology is one app acting as
|
||||
both client (the add-in signs in as it) and resource (your backend validates
|
||||
tokens audienced to it); split into two if your policy requires.
|
||||
|
||||
### 1. Redirect URIs
|
||||
|
||||
*Authentication* → *Add a platform* → **Single-page application** → add
|
||||
**both**:
|
||||
|
||||
| URI | Used by |
|
||||
|---|---|
|
||||
| `brk-multihub://pivot.claude.ai` | [NAA broker](https://learn.microsoft.com/office/dev/add-ins/develop/enable-nested-app-authentication-in-your-add-in) — desktop Office and Outlook web. Missing this → `AADSTS50011` at sign-in. |
|
||||
| `https://pivot.claude.ai/msal-redirect.html` | SPA fallback — Excel/Word/PowerPoint on Office for the web, which don't inject the NAA bridge. |
|
||||
|
||||
Both go under the SPA platform (not Web, not Mobile/desktop).
|
||||
|
||||
### 2. Permissions / API setup
|
||||
|
||||
What you configure here depends on what the token is for:
|
||||
|
||||
| Use case | Configure |
|
||||
|---|---|
|
||||
| **Outlook (Graph)** | *API permissions* → *Microsoft Graph* → Delegated → `Mail.ReadWrite`, `Calendars.Read`, `People.Read`, `User.Read`, `offline_access`. |
|
||||
| **Gateway / bootstrap auth** (`entra_scope`, `gateway_auth_source=entra`) | *Expose an API* → set Application ID URI `api://<app-guid>` → *Add a scope* (e.g. `access_as_user`, admin-consent enabled). Then *API permissions* → *My APIs* → add that scope as a delegated permission (the app to itself, in single-app topology). |
|
||||
| **Bedrock WIF** (`aws_role_arn`) | No API permissions needed — the ID token alone is the web identity. |
|
||||
|
||||
In all cases finish with *API permissions* → **Grant admin consent for
|
||||
<tenant>**. Without it every user sees a consent prompt (or is blocked, if
|
||||
user consent is disabled).
|
||||
|
||||
### 3. Token version
|
||||
|
||||
Only if you set up *Expose an API* above: in the app *Manifest*, set
|
||||
`"accessTokenAcceptedVersion": 2`. Leave it unset and Entra issues v1.0 access
|
||||
tokens (`iss` without `/v2.0`, no `preferred_username`), which most JWT
|
||||
middleware rejects by default.
|
||||
|
||||
## What your backend validates
|
||||
|
||||
For `entra_scope` / `gateway_auth_source=entra`, the access token the add-in
|
||||
sends as `Authorization: Bearer` carries:
|
||||
|
||||
| Claim | Expected |
|
||||
|---|---|
|
||||
| `iss` | `https://login.microsoftonline.com/<tenant-id>/v2.0` |
|
||||
| `aud` | your Application ID URI (`api://<app-guid>`) |
|
||||
| `scp` | the scope(s) you exposed, space-separated |
|
||||
| JWKS | `https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys` |
|
||||
|
||||
## GCC High / DoD / 21Vianet
|
||||
|
||||
Same steps, different portal and endpoints — apps don't replicate across
|
||||
Microsoft national clouds.
|
||||
|
||||
- **Register at** [`portal.azure.us`](https://portal.azure.us) (US Gov) or
|
||||
[`portal.azure.cn`](https://portal.azure.cn) (21Vianet), not the commercial
|
||||
portal.
|
||||
- **Redirect URIs** are unchanged — `brk-multihub://pivot.claude.ai` and
|
||||
`https://pivot.claude.ai/msal-redirect.html`. The add-in is served from the
|
||||
same domain in every cloud.
|
||||
- **Manifest** — add `graph_cloud=us-gov-high` (or `us-gov-dod`, `china`) per
|
||||
the [sovereign clouds](manifest.md#sovereign--national-clouds-gcc-high-dod-21vianet)
|
||||
table.
|
||||
- **Admin consent URL** uses the sovereign authority:
|
||||
`https://login.microsoftonline.us/<tenant-id>/adminconsent?client_id=<app-id>`
|
||||
- **Backend validation** — the token's `iss` and JWKS use the `.us` host:
|
||||
`https://login.microsoftonline.us/<tenant-id>/v2.0` and
|
||||
`https://login.microsoftonline.us/<tenant-id>/discovery/v2.0/keys`. A
|
||||
validator pinned to `.com` rejects every request. The same applies to any AWS
|
||||
OIDC identity provider in the chain.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`AADSTS50011` (redirect URI mismatch)** — one of the two URIs above is
|
||||
missing, or was added under the wrong platform (must be SPA).
|
||||
|
||||
**`Tag: 9n156` on Outlook for Mac** — the host's auth broker can't complete.
|
||||
Almost always a sovereign-cloud account hitting an app registered in commercial
|
||||
(fix: register in `portal.azure.us` and set `graph_cloud`). If the app is
|
||||
already in the right cloud, check Conditional Access — the deprecated *Require
|
||||
approved client app* grant blocks NAA. The Correlation Id in the dialog is
|
||||
resolvable in your Entra sign-in logs, not Anthropic's.
|
||||
|
||||
**`AADSTS65001` (consent required)** — *Grant admin consent* wasn't clicked, or
|
||||
a permission was added after consent was granted (re-grant).
|
||||
@@ -48,9 +48,10 @@ the add-in uses Anthropic's multi-tenant app.
|
||||
The add-in auto-detects the tenant's national cloud at sign-in (from the
|
||||
authority host Office reports) and resolves the matching Graph + Entra
|
||||
endpoints, so most sovereign tenants need **no cloud config**. The only
|
||||
required step is
|
||||
bringing your own Entra app via `graph_client_id` — Anthropic's multi-tenant
|
||||
app exists only in the commercial cloud. A GCC-High Outlook manifest needs
|
||||
required step is bringing your own Entra app via `graph_client_id` —
|
||||
Anthropic's multi-tenant app exists only in the commercial cloud; see
|
||||
[entra-app](entra-app.md#gcc-high--dod--21vianet) for the registration steps in
|
||||
the Azure Government / 21Vianet portals. A GCC-High Outlook manifest needs
|
||||
nothing beyond the usual keys:
|
||||
|
||||
```bash
|
||||
@@ -110,10 +111,9 @@ involve Microsoft.
|
||||
**Bring your own Entra app.** By default the token is requested as Anthropic's
|
||||
multi-tenant app (`c2995f31-…`), so its `aud` claim is that GUID. If your
|
||||
bootstrap endpoint or token-exchange service requires `aud` to match an app
|
||||
registered in *your* tenant, set `graph_client_id=<your-app-guid>`. Register
|
||||
the app in Entra as a single-tenant **Single-page application** with redirect
|
||||
URI `https://pivot.claude.ai/msal-redirect.html`. You handle consent on your
|
||||
own app — [consent](consent.md) covers the default app only.
|
||||
registered in *your* tenant, set `graph_client_id=<your-app-guid>`. See
|
||||
[entra-app](entra-app.md) for the registration steps (redirect URIs, API setup,
|
||||
admin consent). [consent](consent.md) covers Anthropic's default app only.
|
||||
|
||||
**Send an access token instead of the ID token.** With `graph_client_id` alone
|
||||
the add-in still sends an *ID token* to your bootstrap endpoint — `aud` is your
|
||||
@@ -145,6 +145,36 @@ 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.
|
||||
|
||||
## Use the Entra token as your gateway credential
|
||||
|
||||
If your gateway already validates Entra JWTs (`aud` + `scp` against your own
|
||||
API resource), you don't need a separate `gateway_token` or a bootstrap hop —
|
||||
set `gateway_auth_source=entra` and the add-in sends the Entra access token it
|
||||
acquired above directly as `Authorization: Bearer` on every gateway call, and
|
||||
silently re-acquires it before expiry. The end-user experience is zero-input
|
||||
SSO: open the add-in, start chatting.
|
||||
|
||||
```bash
|
||||
node "${CLAUDE_PLUGIN_ROOT}/scripts/build-manifest.mjs" office manifest.xml \
|
||||
gateway_url=https://llm-gateway.your-org.example \
|
||||
entra_sso=1 \
|
||||
graph_client_id=<client-app-guid> \
|
||||
entra_scope=api://<resource-app-guid>/access_as_user \
|
||||
gateway_auth_source=entra
|
||||
```
|
||||
|
||||
`gateway_auth_source=entra` requires `entra_scope` (and therefore
|
||||
`graph_client_id` and `entra_sso=1`); the build script enforces this. It
|
||||
implies `gateway_auth_header=authorization`, so you can omit that key. Don't
|
||||
also set `gateway_token` — it's ignored, and the script warns.
|
||||
|
||||
**Entra setup:** see [entra-app](entra-app.md) — the *Gateway / bootstrap auth*
|
||||
row of the permissions table, plus the
|
||||
[backend validation](entra-app.md#what-your-backend-validates) section for the
|
||||
`iss`/`aud`/`scp`/JWKS values your gateway should check. GCC High / DoD
|
||||
deployments are covered in the
|
||||
[same doc](entra-app.md#gcc-high--dod--21vianet).
|
||||
|
||||
## Bootstrap endpoint
|
||||
|
||||
`bootstrap_url` points to an HTTPS endpoint you host. At startup the add-in
|
||||
|
||||
@@ -46,6 +46,10 @@ const KEYS = {
|
||||
gateway_token: { pattern: /./, hint: "gateway API key", secret: true },
|
||||
gateway_auth_header: { pattern: /^(x-api-key|authorization)$/i, hint: "auth header scheme (default: x-api-key)" },
|
||||
gateway_api_format: { pattern: /^(anthropic|bedrock|vertex)$/i, hint: "anthropic | bedrock | vertex" },
|
||||
gateway_auth_source: {
|
||||
pattern: /^entra$/,
|
||||
hint: "'entra' to use the Entra SSO access token as the gateway Bearer (no gateway_token needed); requires entra_scope",
|
||||
},
|
||||
mcp_servers: { pattern: /^\[.*\]$/, hint: "JSON array of {url, label, headers?, discover?}" },
|
||||
inference_headers: { pattern: /^\{.*\}$/, hint: "JSON object of extra headers to attach to every model request" },
|
||||
bootstrap_url: { pattern: /^https:\/\//, hint: "HTTPS endpoint returning per-user config" },
|
||||
@@ -83,7 +87,7 @@ const KEYS = {
|
||||
},
|
||||
};
|
||||
|
||||
const NEEDS_ENTRA = ["aws_role_arn", "graph_client_id", "entra_scope"];
|
||||
const NEEDS_ENTRA = ["aws_role_arn", "graph_client_id", "entra_scope", "gateway_auth_source"];
|
||||
|
||||
async function main() {
|
||||
const [host, out, ...pairs] = process.argv.slice(2);
|
||||
@@ -97,10 +101,7 @@ async function main() {
|
||||
console.error("error: Amazon Bedrock (aws_role_arn/aws_region) is not currently supported for Outlook");
|
||||
process.exit(1);
|
||||
}
|
||||
if (host !== "outlook" && pairs.some((p) => p.startsWith("graph_client_id="))) {
|
||||
console.warn("note: graph_client_id only applies to Outlook; it has no effect in the office manifest");
|
||||
}
|
||||
// graph_cloud is relevant to both manifests: in Outlook it steers Graph +
|
||||
// graph_client_id and graph_cloud apply to both manifests: in Outlook it steers Graph +
|
||||
// Entra sign-in; in office it steers the Entra SSO authority.
|
||||
|
||||
const params = new URLSearchParams();
|
||||
@@ -128,6 +129,14 @@ async function main() {
|
||||
if (params.has("entra_scope") && !params.has("graph_client_id")) {
|
||||
throw new Error("entra_scope requires graph_client_id (the scope is requested as your own Entra app, not the default)");
|
||||
}
|
||||
if (params.has("gateway_auth_source") && !params.has("entra_scope")) {
|
||||
throw new Error(
|
||||
"gateway_auth_source=entra requires entra_scope (the gateway Bearer must be audienced to your API, not Microsoft Graph)",
|
||||
);
|
||||
}
|
||||
if (params.has("gateway_auth_source") && params.has("gateway_token")) {
|
||||
console.warn("note: gateway_auth_source=entra supersedes gateway_token — drop gateway_token from this manifest");
|
||||
}
|
||||
// A non-global graph_cloud needs a BYO Entra app — Anthropic's multi-tenant
|
||||
// app exists only in the commercial cloud, so the default client_id against
|
||||
// a sovereign authority fails with an opaque AADSTS700016 at sign-in.
|
||||
|
||||
Reference in New Issue
Block a user