Add auth troubleshooting guide; remove unsupported license-key gate; link Adaptive Card Validator#247
Add auth troubleshooting guide; remove unsupported license-key gate; link Adaptive Card Validator#247brandonpollett wants to merge 4 commits into
Conversation
…d Adaptive Card Validator references Adds a partner-facing authentication troubleshooting guide and removes the license-key gate from the sample extension (the platform does not support license-key auth, so its inclusion was confusing partners). Authentication troubleshooting guide - New doc/Troubleshooting-Authentication.md: symptom -> cause matrix, deep dives (InvalidAudience/InvalidIssuer/InvalidApplicationId, no-traffic / WAF / network ACLs, AADSTS500011, customer identity model, Application ID URI / identifierUris mistakes), FAQ, pre-flight checklist, and diagnostic bundle to attach to support tickets. - Linked from doc/Authentication.md, doc/AuthenticationDesign.md, and physician/QUICKSTART.md so partners hitting auth errors land on it from any of the obvious entry points. License-key cleanup (sample extension) - Deleted LicenseKeyMiddleware.cs and LicenseKeyOptions.cs (and the now-empty Middleware/ folder). - Removed the LicenseKey middleware from UseFullSecurity, the LicenseKey Swagger security definition, and the LicenseKeyOptions DI registration. - Removed the LicenseKey sections from appsettings.json and appsettings.Development.json. - Updated KnownRoutes/ProcessController/Program.cs comments and XML docs. - Rewrote doc/Authentication.md as JWT-only (single-gate); updated doc/AuthenticationDesign.md overview and .github/copilot-instructions.md. Adaptive Card Validator references - Added a tip linking to https://cardvalidator.copilot.dragon.com/ from physician/QUICKSTART.md, the Python sample README, and the AdaptiveCardPayload.cs XML doc (for IntelliSense discovery). Build verified: dotnet build of SampleExtension.Web -> 0 warnings, 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the Dragon Copilot Extension samples and docs to reflect the platform’s actual security model (JWT/Entra-only), adds a partner-facing authentication troubleshooting guide, and improves discoverability of the Adaptive Card Validator.
Changes:
- Added a new authentication troubleshooting guide and linked it from existing auth docs and the Physician quickstart.
- Removed the sample extension’s license-key middleware/options and related configuration/docs (JWT-only).
- Added references to the Adaptive Card Validator in docs and in
AdaptiveCardPayloadXML remarks.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Program.cs | Updates pipeline comment to reflect JWT-only security. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Middleware/LicenseKeyMiddleware.cs | Removes unsupported license-key middleware. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Extensions/WebApplicationExtensions.cs | Removes license-key middleware and Swagger security scheme wiring. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Extensions/ServiceCollectionExtensions.cs | Removes DI options wiring for license-key configuration. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Controllers/ProcessController.cs | Updates 403 response documentation to claims-based authorization. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Configuration/LicenseKeyOptions.cs | Removes unsupported license-key options type. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/Configuration/KnownRoutes.cs | Updates public-route comment to match JWT-only model. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/appsettings.json | Removes LicenseKey configuration block. |
| physician/src/samples/DragonCopilot/Workflow/SampleExtension.Web/appsettings.Development.json | Removes LicenseKey configuration block. |
| physician/src/samples/DragonCopilot/Workflow/pythonSampleExtension/README.md | Adds Adaptive Card Validator link for debugging card output. |
| physician/src/models/Dragon.Copilot.Physician.Models/AdaptiveCardPayload.cs | Adds XML remarks linking to Adaptive Card Validator. |
| physician/QUICKSTART.md | Links auth troubleshooting guide and adds Adaptive Card Validator tip. |
| doc/Troubleshooting-Authentication.md | Adds new partner-facing auth troubleshooting guide. |
| doc/AuthenticationDesign.md | Updates overview to JWT-only and links troubleshooting guide. |
| doc/Authentication.md | Rewrites as JWT-only and links troubleshooting guide. |
| .github/copilot-instructions.md | Updates repo guidance to JWT-only authentication. |
| **Checks:** | ||
| 1. `iss` must be exactly `https://login.microsoftonline.com/{your-tenant-id}/` — note the trailing slash and v2.0 endpoint format. | ||
| 2. Confirm the tenant ID configured in your JWT validator matches the `auth.tenantId` in your `extension.yaml` manifest. The two values **must** match — the `dragon-copilot` CLI prompts for the same value to keep them aligned. | ||
| 3. Do not use the `common` or `organizations` authority for a single-tenant extension. Pin the validator to your tenant GUID. |
There was a problem hiding this comment.
Good catch. Fixed in 8282803: §2.2 now reads https://login.microsoftonline.com/{tenant}/v2.0 (the /v2.0 suffix is the v2 issuer that pairs with
equestedAccessTokenVersion: 2). The matrix row and pre-flight checklist were updated to match.
| **Mental model:** the platform does not "send" an audience. It acquires a token using `identifierUri` **as the scope**, and the resulting `aud` claim is derived from it. If `identifierUri` does not line up with the actual endpoint host, the token your extension receives will have an `aud` that cannot match a sensible validation rule. | ||
|
|
There was a problem hiding this comment.
Agreed — this was the root issue across all three of your comments. §2.7's mental-model paragraph is rewritten in 8282803: identifierUri is the scope the Runtime requests, and with v2 tokens aud is the Client ID GUID, not the URI. A scope mismatch now correctly attributes to "token acquisition fails → no traffic" (pointing at §2.4), not InvalidAudience.
| ```text | ||
| 1. Read the endpoint host from extension.yaml (tools[].endpoint). | ||
| 2. Read the identifierUris array from your Entra app registration manifest. | ||
| 3. Confirm the array contains exactly: api://{your-tenant-id}/{that-host}. | ||
| 4. Decode a real token at https://jwt.ms and confirm aud equals that string. | ||
| ``` | ||
|
|
||
| **Validation rule for your extension code:** | ||
| Reject the request if `aud` does not equal `api://{your-tenant-id}/{Host header of the request}`. This prevents accidental cross-environment token replay. | ||
|
|
There was a problem hiding this comment.
Agreed. Rewritten in 8282803: the "Validation rule for your extension code" block (which recommended deriving expected aud from the HTTP Host header) is removed. The quick-validation procedure now says to confirm aud == Client ID GUID, azp == Runtime client ID, and iss == .../{tenant}/v2.0 — matching AuthenticationDesign.md and the sample's validator.
The validator preview pane silently failed (Invalid Card) on the sample's output even though structural validation reported Valid JSON. Two issues: 1. AdaptiveCardPayload.Version was hard-coded to "1.3". The Dragon Copilot card spec does not require �ersion; the validator's reference sample omits it entirely. Made Version nullable and tagged it with JsonIgnore(Condition = WhenWritingNull) so it is omitted by default. Partners can still set a specific version if they need one. 2. VisualizationResource.References was being emitted as [] (empty array). The validator treats an empty eferences array as a render- blocker (silent fail) and a missing eferences field as a hard error (Missing property "references"). Populated the sample with a Web reference pointing at the published adaptive-card-spec page so partners see a working reference end-to-end. Also tagged the nullable string fields on VisualizationReference (SectionId, Text, Title, Url) with JsonIgnore(WhenWritingNull) so a Web-typed reference no longer serializes sectionId: null / ext: null (which the validator flags as "Property X is not allowed"). XML doc updates: - AdaptiveCardPayload: validator link now also notes the aka.ms shortlink and links to the official adaptive-card-spec. - AdaptiveCardPayload.Version: documents that the field is optional and should normally be omitted. - VisualizationResource.References: warns that an empty array causes the validator preview to fail silently and links to the spec. Verified end-to-end against https://cardvalidator.copilot.dragon.com/: status Valid JSON, zero warnings, full preview renders (title, all five action buttons, references footer). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…alidator
Mirrors the C# fix in the previous commit. Without these changes the
validator preview pane silently failed (`Invalid Card`) on the python
sample's output even though structural validation reported `Valid JSON`.
Changes in `app/service.py`:
- Removed the hardcoded `"version": "1.6"` from the active adaptive
card payload (and from the two commented-out alternate cards). The
Dragon Copilot card spec does not require `version` and the validator's
reference sample omits it.
- Replaced `references=[]` with a `Web`-typed reference pointing at
the published adaptive-card-spec page. The validator treats an empty
`references` array as a render-blocker (silent fail).
- Added inline comments at both sites explaining why `version` is
omitted and why `references` must be non-empty.
- Applied the same fix to the two commented-out example cards so any
future re-enablement starts from a working baseline.
Test update in `app/tests/test_adaptive_card.py`:
- Replaced `assert ac.get("version") == "1.6"` with
`assert "version" not in ac` to lock in the new behavior.
Verified end-to-end against https://cardvalidator.copilot.dragon.com/:
status `Valid JSON`, zero warnings, preview renders both action buttons
and the references footer. All 5 pytest tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three related fixes to doc/Troubleshooting-Authentication.md, all stemming from the same root mistake: §2.7 had framed the JWT `aud` claim as being derived from `identifierUri`, which is only true for v1 tokens. With `requestedAccessTokenVersion: 2` (which this repo explicitly recommends), `aud` is the extension's Client ID GUID and `identifierUri` only appears as the *scope* the Runtime requests when acquiring a token. Changes: - §2.2 / matrix / pre-flight checklist: corrected the expected v2.0 issuer to `https://login.microsoftonline.com/{tenant-id}/v2.0` (added the missing `/v2.0` suffix that pairs with v2 tokens). - §2.7: rewrote the "Mental model" paragraph to state that `identifierUri` is the scope, and that a mismatch causes token acquisition to fail (symptom: no traffic — point at §2.4) rather than `InvalidAudience` on the extension side. - §2.7 "Quick validation procedure" now tells partners to verify `aud == Client ID GUID`, `azp == Runtime client ID`, and `iss == .../{tenant-id}/v2.0` — matching the sample extension's validator and the guidance in AuthenticationDesign.md. - §2.7: removed the "Validation rule for your extension code" block that recommended deriving expected `aud` from the HTTP Host header (which contradicted the v2-token guidance and would have encouraged unsafe host-header-trusting validation logic). - Matrix: split the old "InvalidAudience even though Client ID looks correct" row into two rows that correctly attribute (a) `aud` containing the App ID URI -> v1 tokens (points at §2.1) and (b) no-traffic / token-acquisition failure -> malformed `identifierUris` (points at §2.7). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds a partner-facing authentication troubleshooting guide, removes the license-key gate from the sample extension (the platform does not actually support license-key auth), and links the new Adaptive Card Validator from the spots partners are most likely to land.
Authentication troubleshooting guide
doc/Troubleshooting-Authentication.md— symptom -> cause matrix, deep dives covering:401 InvalidAudience/InvalidIssuer/InvalidApplicationIdAADSTS500011— Runtime service principal missing in tenantidentifierUrismistakes — top source ofInvalidAudienceafter partners think everything else is correctdoc/Authentication.md,doc/AuthenticationDesign.md, andphysician/QUICKSTART.mdLicense-key cleanup
The platform doesn't support license-key auth — its presence in the sample was misleading partners. Fully removed:
LicenseKeyMiddleware.cs,LicenseKeyOptions.cs, and the now-emptyMiddleware/folderUseFullSecurity, the Swagger security definition, and the DI registrationLicenseKeysections fromappsettings.jsonandappsettings.Development.jsondoc/Authentication.mdas JWT-only (single gate); updateddoc/AuthenticationDesign.mdoverview and.github/copilot-instructions.mdAdaptive Card Validator
Added discoverable references to https://cardvalidator.copilot.dragon.com/ in:
physician/QUICKSTART.md— next to the existing Adaptive Card Specification linkphysician/.../pythonSampleExtension/README.md— next to theadaptive_card_payloadexample responsephysician/src/models/.../AdaptiveCardPayload.cs—<remarks>XML doc (IntelliSense discovery)Verification
dotnet buildofSampleExtension.Web-> Build succeeded. 0 Warning(s). 0 Error(s).LicenseKey/license key/X-License-Key/ValidKeysanywhere in the repo.