You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8bdf02e
Browse filesBrowse the repository at this point in the historyBrowse files
Support OAuth client credential location and scopes (#169)
* Support OAuth client credential location and scopes
Adds OAuth 2.0 client credentials configuration for SCIM onboarding. Customers can select whether credentials are sent through the HTTP Basic Authorization header or request body, with Header as the default. Customers can also provide an optional space-delimited OAuth scope string. Updates the Logic App OAuth validation request, Entra connectivity parameters, onboarding agent instructions, setup documentation, validation overview, and bumps the Standard Logic App version to 13.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Encode OAuth Basic credentials per RFC 6749
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5c32705b-7e16-4b9e-8061-95f225ffc593
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Garima Garg <ggarima01@users.noreply.github.com>
Copilot-Session: 5c32705b-7e16-4b9e-8061-95f225ffc593
Copy file name to clipboardExpand all lines: Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIM-Validation-Test-Overview.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,7 +67,7 @@ Tests that don't apply are reported as **SKIPPED** (not failures). However, grou
67
67
| 18 |**SCIM_User_Update_Test**| mandatory | Direct `PATCH /Users/{id}` with auto-generated update values for eligible attributes (single-valued, string/integer/datetime/boolean, excluding groups/roles/id/schemas/meta/active). | PATCH returns 200/204; subsequent GET returns 200/204; all PATCHed attribute values match in the response; zero mismatches. Passes with `(no updatable attributes)` if no eligible attributes exist. |
68
68
| 19 |**SCIM_Group_Create_Test**| mandatory | Direct `POST /Groups` with SCIM group body. **FAILED** if groups not supported. | HTTP 200 or 201; response contains non-empty `id`; returned `displayName` matches sent `displayName`. |
69
69
| 20 |**SCIM_Group_Update_Test**| mandatory | Direct `PATCH /Groups/{id}` with attribute updates (single-valued, non-reference, excluding id/members/schemas/meta). | PATCH returns 200/204; GET returns 200/204; all PATCHed attributes match; zero mismatches. |
70
-
| 21 |**Validate_Credentials_Test**| mandatory | OAuth 2.0 Client Credentials flow — acquires token from ISV's `scimTokenEndpoint`, then validates SCIM access with the token. **Skipped** when `scimTokenEndpoint` is empty (static bearer token). | Token endpoint returns 2xx with `access_token`; token validity between 60–360 minutes; SCIM endpoint returns 2xx when called with the token. |
70
+
| 21 |**Validate_Credentials_Test**| mandatory | OAuth 2.0 Client Credentials flow — acquires a token from the ISV's `scimTokenEndpoint`, sends credentials in the required `Header` or `Body` location, includes the optional space-delimited scope string, then validates SCIM access with the token. **Skipped** when `scimTokenEndpoint` is empty (static bearer token). | Token endpoint returns 2xx with `access_token`; token validity between 60–360 minutes; SCIM endpoint returns 2xx when called with the token. |
71
71
| 22 |**Federated_Identity_Test**| mandatory | Workload identity federation — acquires Entra ID token, then exchanges it for ISV token via one of three flows: Google Service Account, Google STS, or generic federated endpoint. **Skipped** when federated parameters are empty. | Entra token acquisition returns 2xx with `access_token`; federated token exchange returns 2xx with valid token. Supports Google SA flow (STS exchange → SA impersonation), Google STS flow (direct exchange), and generic flow (client assertion). |
72
72
| 23 |**SCIM_User_Pagination_Test**| mandatory | Ensures ≥11 users exist (creates throwaway users if needed), then paginates `GET /Users?startIndex=N&count=5` across pages. Cleans up created users afterward. | At least 2 pages traversed; all pages return HTTP 200; response `startIndex` matches expected value (`page * 5 + 1`); zero failed page verifications. |
73
73
| 24 |**SCIM_Group_Pagination_Test**| optional | Same as user pagination but for `/Groups`. Failure = WARNING. **Skipped** if groups not supported. | At least 2 pages traversed; all pages return HTTP 200; response `startIndex` matches expected; zero failed verifications. |
- How credentials are sent — **MANDATORY:** use `ask_user` with exactly two options: `Header` and `Body`. Set `Header` as the suggested default, but require the customer to confirm their selection.
62
+
-`Header` sends the client ID and secret using HTTP Basic authentication and omits them from the form body.
63
+
-`Body` sends `client_id` and `client_secret` in the form body.
61
64
- OAuth scope — **MANDATORY: use this EXACT prompt text in `ask_user`, do NOT paraphrase, do NOT use the words "leave blank", "leave empty", "optional", or "if not required":**
> **OAuth scope** (e.g., `https://graph.microsoft.com/.default`). Enter multiple scopes as one string separated by spaces.
63
66
>
64
67
> ⚠️ If your token endpoint does NOT require a scope, type the word `none` (without quotes) and press Enter.
65
68
>
66
69
> Do NOT submit an empty box — empty submissions are treated as cancellation and the agent will stop.
67
70
68
71
The agent treats `none` (case-insensitive) as an empty scope when writing `scimOAuthScope` to `parameters.json`.
69
-
- If **static bearer token**: record `authMethod = bearer`. The 4 OAuth fields (`scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, `scimOAuthScope`) will be written as empty strings in Phase 4. (Logic App test behavior — including `Validate_Credentials_Test` — is out of scope for Phase 1; see Phase 4 for parameter handling and expected test behavior.)
72
+
- If **static bearer token**: record `authMethod = bearer`. The 5 OAuth fields (`scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, `scimCredentialLocationInRequest`, `scimOAuthScope`) will be written as empty strings in Phase 4. (Logic App test behavior — including `Validate_Credentials_Test` — is out of scope for Phase 1; see Phase 4 for parameter handling and expected test behavior.)
70
73
71
74
d. **Federated identity test inputs (for `Federated_Identity_Test`)**:
72
75
- Ask: "Do you want to run the federated identity validation test now?"
@@ -216,6 +219,8 @@ Extract from response:
216
219
| Bearer only | A | Bearer |`authenticationType` + `baseAddress` + `secretToken`|
For Branch B, also include the optional lower-case `scope` connectivity parameter when the ISV provided scopes. Omit `scope` when the ISV answered `none`.
223
+
219
224
---
220
225
221
226
**Sub-step 2b-1: validateCredentials (bearer — ALWAYS run this)**
> Note: `authenticationType=OAuth2ClientCredentialsGrant` is required for OAuth — without it, Graph won't attempt OAuth token acquisition from the stored creds.
287
293
>
294
+
> `credentialLocationInRequest` is required for OAuth and must be exactly `Header` or `Body`, matching the ISV's Phase 1 answer. Add the `scope` entry only when the ISV provided one or more scopes; omit it when the ISV answered `none`. Preserve multiple scopes as one space-delimited string.
295
+
>
288
296
> Inline `validateCredentials` with OAuth keys (i.e. `useSavedCredentials: false` + credentials array including OAuth keys) **works**, but do **not** include `CredentialLocationInRequest` in that inline payload — Graph returns `InternalError: Requested value 'CredentialLocationInRequest' was not found.` Keep that key only in the beta `connectivityParameters` PATCH payload.
289
297
290
298
Then validate saved credentials work (both branches) — **this is the Test Connection**:
@@ -303,7 +311,7 @@ az rest --method POST \
303
311
|---|---|---|
304
312
| HTTP 200/204 (empty body) | Test Connection succeeded — Entra can reach the ISV's SCIM endpoint using the saved credentials | Proceed to 2b-3 |
305
313
| HTTP 400 `CredentialValidationUnavailable`|**Branch A:** bearer token rejected by the ISV's SCIM server (401/403/5xx). **Branch B:** OAuth token exchange failed — wrong client ID, wrong client secret, bad token endpoint URL, scope issue, or the ISV's token endpoint issued a token that their SCIM server rejected. | Surface the inner error verbatim to the ISV. **ABORT.** Do NOT create the sync job — it will immediately quarantine. Wait for the ISV to provide corrected credentials, then re-PATCH `connectivityParameters` and re-validate. |
306
-
| HTTP 500 `InternalError` — `"Requested value 'X' was not found"`| Inline validation payload included an unsupported key (for example `CredentialLocationInRequest`) or the connectivity parameters used the wrong key casing | Re-run inline validation with only the supported OAuth keys, then re-PATCH `connectivityParameters` using the lower-case portal keys (`authenticationType`, `baseAddress`, `oauth2ClientId`, `oauth2ClientSecret`, `oauth2TokenExchangeUri`, `credentialLocationInRequest`). Then re-validate. |
314
+
| HTTP 500 `InternalError` — `"Requested value 'X' was not found"`| Inline validation payload included an unsupported key (for example `CredentialLocationInRequest`) or the connectivity parameters used the wrong key casing | Re-run inline validation with only the supported OAuth keys, then re-PATCH `connectivityParameters` using the lower-case portal keys (`authenticationType`, `baseAddress`, `oauth2ClientId`, `oauth2ClientSecret`, `oauth2TokenExchangeUri`, `credentialLocationInRequest`, plus `scope` only when provided). Then re-validate. |
307
315
| Any other 4xx/5xx | Unexpected | Surface verbatim and **ABORT**. |
308
316
309
317
Before aborting or retrying, capture the exact inner error and match it against **Step 6d: Known Issues**. If the response mentions `SystemForCrossDomainIdentityManagementServiceIncompatible`, Group connectivity, or an inner Group `404`, evaluate **Pattern #15**. Do not classify a generic `401`, `403`, rejected bearer token, or OAuth token-exchange failure as Pattern #15.
For federated identity testing, also pass these Phase 4 parameters:
362
371
@@ -822,7 +831,7 @@ Pass criteria (ALL must hold):
822
831
- `schedule.state` is `Active`
823
832
824
833
If `status.code == Quarantine` with `lastExecError == SystemForCrossDomainIdentityManagementInvalidCredentials` and `lastExecMsg` mentions `BaseAddress`/`SecretToken`/credential, the Step 2b connectivity parameters were rejected or incomplete. Recovery:
825
-
1. Re-PATCH `https://graph.microsoft.com/beta/servicePrincipals/<servicePrincipalId>/synchronization/connectivityParameters` with ONLY the supported keys for the auth mode (bearer: `authenticationType` + `baseAddress` + `secretToken`; OAuth: `authenticationType` + `baseAddress` + `oauth2ClientId` + `oauth2ClientSecret` + `oauth2TokenExchangeUri` + `credentialLocationInRequest`).
834
+
1. Re-PATCH `https://graph.microsoft.com/beta/servicePrincipals/<servicePrincipalId>/synchronization/connectivityParameters` with ONLY the supported keys for the auth mode (bearer: `authenticationType` + `baseAddress` + `secretToken`; OAuth: `authenticationType` + `baseAddress` + `oauth2ClientId` + `oauth2ClientSecret` + `oauth2TokenExchangeUri` + `credentialLocationInRequest`, plus `scope` only when provided).
826
835
2. `POST /servicePrincipals/<sp>/synchronization/jobs/<jobId>/restart` with body `{"criteria":{"resetScope":"Full"}}`. (Note: `credentials`/`watermark`/`escrows`/`quarantineState` are NOT valid restart criteria properties — schema only allows `resetScope`.)
827
836
3. `POST /jobs/<jobId>/start` again.
828
837
4. Re-run this Step 3h check. If still quarantined, abort and report the exact error.
@@ -907,7 +916,8 @@ Update these parameters in the JSON:
907
916
| `scimClientId` | `<ISV's OAuth client ID>`| From Phase 1 — setif the ISV provided OAuth credentials. Used by the LA's `Validate_Credentials_Test` to exercise the OAuth flow independently. Empty string if not provided. **Note: Entra sync always uses bearer token (Step 2b), NOT these OAuth values.** |
908
917
| `scimClientSecret` | `<ISV's OAuth client secret>`| From Phase 1 — same. Empty string if not provided. |
909
918
|`scimTokenEndpoint`|`<ISV's OAuth token endpoint>` | From Phase 1 — same. Empty string if not provided. |
910
-
| `scimOAuthScope` | `<ISV's OAuth scope>`| From Phase 1 — optional, setif provided (empty string if not). |
919
+
| `scimCredentialLocationInRequest` | `<Header-or-Body>` | From Phase 1 — mandatory when OAuth client credentials are provided. Empty string for static bearer authentication. |
920
+
| `scimOAuthScope` | `<ISV's space-delimited OAuth scopes>`| From Phase 1 — optional; preserve multiple scopes as one space-delimited string. Empty string if the ISV answered `none`. |
911
921
|`federatedEntraTenantId`|`<ISV's Entra tenant ID for federated test>` | From Phase 1 federated inputs — empty string if not provided. |
912
922
| `federatedApplicationId` | `<Entra app client ID used for federated flow>` | From Phase 1 federated inputs — empty string if not provided. |
913
923
| `federatedApplicationClientSecret` | `<Entra app client secret used for federated flow>` | From Phase 1 federated inputs — empty string if not provided. |
@@ -924,7 +934,8 @@ After patching the JSON in memory, assert every key below exists at the top leve
@@ -935,7 +946,7 @@ If any key is missing, abort Phase 4 and tell the ISV exactly which key is missi
935
946
936
947
After the PUT completes, re-GET `parameters.json` and for each key in the patch table above, assert the returned value matches what was sent. If `servicePrincipalId` was supposed to be `aaa-bbb-ccc` but the read-back shows something else (or the key is missing), abort Phase 4 with the specific mismatch. Do NOT proceed to Phase 5.
937
948
938
-
If the ISV did not provide OAuth credentials, leave `scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, and `scimOAuthScope` as empty strings. The `Validate_Credentials_Test` will be SKIPPED — note this as expected in the final report. This is unrelated to the Entra sync engine, which always uses the bearer token (`SecretToken`) configured in Step 2b.
949
+
If the ISV did not provide OAuth credentials, leave `scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, `scimCredentialLocationInRequest`, and `scimOAuthScope` as empty strings. The `Validate_Credentials_Test` will be SKIPPED — note this as expected in the final report. This is unrelated to the Entra sync engine, which always uses the bearer token (`SecretToken`) configured in Step 2b.
939
950
940
951
If the ISV did not provide federated identity inputs, leave all federated parameters as empty strings (`federatedEntraTenantId`, `federatedApplicationId`, `federatedApplicationClientSecret`, `federatedTokenEndpoint`, `federatedClientId`, `federatedBaseAddress`, `federatedAudience`).
0 commit comments