Commit 302520d
authored
Enhanced claims processing for MS SQL Set session context (#2003)
# Improved Claims Handling for DB Policies and MS SQL Session Context
## Why make this change?
Initially reported in #1957, DAB does not gracefully handle instances
where the provided JWT token has claims with value type JSON Array.
Two negative impacts:
1. Developers who don't override the default MS SQL
`set-session-context` value of `true` to `false` will observe that
requests fail for tokens that fit the above criteria.
2. Developers who write database policies with `@claims.claimType`
references will see requests fail when the claimType referenced fits the
above criteria.
**Note:** The issue raised in #1957 is unique because the user may be
using a historical version of Duende's IdentityServer which emits token
scopes in a `scope` claim here the value is a JSON array. That format
differs from Entra ID which emits tokens scopes in the 'scp' claim whose
value is a string of values delimited by spaces. Reference: [Entra ID
Access Token Claims
Reference](https://learn.microsoft.com/entra/identity-platform/access-token-claims-reference#payload-claims).
## What changes are introduced in this PR?
### 1. Prevents `@claims.claimType` references in a DB policy from
failing a request when the claimType has a value type of JSON array.
**Example:**
Given the database policy: `@claims.groups eq @item.groupid`, DAB would
previously (before this PR's changes) fail a request if the provided
access token had >1 group because the `groups` claim is a JSON string
array in the JWT token which dotnet resolves into multiple `Claim`
objects. (one claim per group where claimType is `groups` and value is
`groupGUID`. The request failure no longer occurs.
**How is this implemented?**
DAB now recognizes when multiple `Claim` objects exist for a single
claim type. Given the policy `@claims.groups eq @item.groupid` DAB will
replace `@claims.groups` with the first instance of the claimType
`groups` that DAB finds. If DAB has resolved two `groups` claim objects
such as `["groupGUID1", "groupGUID2"]`, DAB would only resolve the first
it finds: `groupGUID1`.
When/if implemented, issue #2004 addresses this behavior by adding the
OData operator `in` so that the query predicate is generated to be
`([dbo].[groupid] in ('tokenGroupGUID1', 'tokenGroupGUID2'))`.
### 2. Prevents multiple instances of a claimType from failing a request
utilizing MSSQL's `set-session-context` feature
**How?**
Aggregates multiple `Claim` objects of the same `claimType` (claim name)
into a JSON array serialized into a string. The original value type in
the JWT JSON array (bool, int, string) is preserved. DAB uses the
serialized JSON as the value for a session context variable whose key is
`claimType`.
When dotnet processes a JWT token, claims whose values are JSON arrays
will be split into distinct `Claim` objects where `claimType` is the
claim name and `value` is one of the values in the array. `Claim`
objects are created for each object in the array. DAB uses the session
context feature to pass token claims and claim values to the database as
session context variables.
#### This is not a breaking change.
This is not a breaking change because access tokens that had claims
which didn't result in DAB failing the request will still have the claim
values passed as is -> scalar values. This is because usable tokens
didn't contain claims with JSON arrays as values.
A breaking change would be modifying this behavior to pass the scalar by
its original type as present in the JWT token by setting
`DbConnectionParam.DbType` explicitly when creating `DbConnectionParam`.
#### How can developers write security policies in SQL?
When the value of a session context variable is a serialized JSON string
containing JSON array, the value can be processed using the following
tsql functionality:
- `JSON_QUERY`(SQL Server 2016(13.X) and later, SQL MI, Azure SQL
Database, Azure Synapse Analytics)
- `JSON_VALUE` (SQL Server 2016(13.X) and later, SQL MI, Azure SQL
Database, Azure Synapse Analytics)
- `JSON_ARRAY` (SQL Server 2022 (16.X), Azure SQL Database)
#### Example JWT token that is now handled without error
```json
{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/25fd4421-0fff-4ed6-ad2f-c2ca00ed7207/",
"iat": 1706642510,
"acr": "1",
"int_array": [1,2,3],
"bool_array": [true, false, true],
"groups":"src1",
"_claim_sources":{
"src1" : { "endpoint" : "https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects" }},
"wids": ["d74b8d81-39eb-4201-bd9f-9f1c4011e3c9","18d14519-c4da-4ad4-936d-9a2de69d33cf","9e513fc0-e8af-43b1-a6c7-949edb1967a3"],
"roles": ["anonymous", "authenticated", "myCustomRole"],
"scp": "email openid profile User.Read",
"scope": ["idServerScope1", "idServerScope2"]
}
```
#### Example tsql created by dab for session context
The following SQL shows how an Entra ID `scp` claim is still passed as a
space delimited string and
how a `roles` claim, a JSON array in the JWT token, is properly
serialized into a JSON array string and passed to MS SQL session context
without the request failing:
Assumption: `x-ms-api-role` header value is `authenticated`
```sql
EXEC sp_set_session_context 'roles', @session_param0, @read_only = 1;
EXEC sp_set_session_context 'scp', @session_param1, @read_only = 1;
EXEC sp_set_session_context 'scopes', @session_param2, @read_only = 1;
SELECT TOP 1 [dbo_books].[id] AS [id], [dbo_books].[title] AS [title], [dbo_books].[publisher_id] AS [publisher_id] FROM [dbo].[books] AS [dbo_books] WHERE [dbo_books].[id] = @param0 ORDER BY [dbo_books].[id] ASC FOR JSON PATH, INCLUDE_NULL_VALUES,WITHOUT_ARRAY_WRAPPER
@param0 int,
@param1 nvarchar(2),
@Param2 nvarchar(5),
@param3 nvarchar(12),
@session_param0 nvarchar(13),
@session_param1 nvarchar(37)
@session_param2 nvarchar(44)
@param0=1,
@param1=N'id',
@Param2=N'title',
@param3=N'publisher_id',
@session_param0=N'["Authenticated"]',
@session_param1=N'GraphQL.ReadWrite REST.EndpointAccess'
@session_param2=N'["GraphQL.ReadWrite", "REST.EndpointAccess"]'
```
#### Matrix of how jwt claims are process by DAB and dotnet for session
context
`Dictionary<string, string> GetProcessedUserClaims(HttpContext?
context)` is populated based on the following rules:
| Jwt ClaimName | JwtClaimValue |
|--------------------|-------------------------------------------------------------------------------------------|
| scp | openid profile customScope |
| scopes (idserver4) | ["openid", "profile","customScope"] |
| intArrayClaim | [1,2,3] |
| boolArrayClaim | [true, false, true] |
| nullValueClaim | null |
| _claim_sources | {"src1":
{"endpoint":"https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects"}}
|
| DotNetProcessed Name | DotNet Processed Value |
|----------------------------------------------|-------------------------------------------------------------------------------------------|
| scp | openid profile customScope |
| scopes </br> scopes </br> scopes | openid </br> profile </br>
customScope |
| intArrayClaim </br> intArrayClaim </br> intArrayClaim | 1 </br> 2
</br> 3 |
| boolArrayClaim </br> boolArrayClaim </br> boolArrayClaim | true </br>
false </br> true |
| nullValueClaim | //empty string |
| _claim_sources | {"src1":
{"endpoint":"https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects"}}
|
| ProcessedValueForSessionCtx | Remarks |
|-------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| openid profile customScope | scp claim from Entra ID access token |
| ["openid", "profile", "customScope" | Non-Entra ID identity providers
may emit a "scopes" claim as a JSON string array. Dotnet processes the
array into individual scope claims and doesn't retain the space
delimited string. |
| [1,2,3] | |
| [true, false, true] | |
| | |
| {"src1":
{"endpoint":"https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects"}}
| |
## Tests
-[x] Unit tests
-[x] Integration tests1 parent a64c277 commit 302520d
File tree
4 files changed
+521
-90
lines changed- src
- Core
- Authorization
- Resolvers
- Service.Tests
- Authentication/Helpers
- Authorization
4 files changed
+521
-90
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
190 | 191 | | |
191 | 192 | | |
192 | 193 | | |
193 | | - | |
| 194 | + | |
194 | 195 | | |
195 | 196 | | |
196 | 197 | | |
| |||
438 | 439 | | |
439 | 440 | | |
440 | 441 | | |
441 | | - | |
442 | | - | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
443 | 446 | | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
444 | 541 | | |
445 | | - | |
446 | | - | |
| 542 | + | |
| 543 | + | |
447 | 544 | | |
448 | | - | |
| 545 | + | |
449 | 546 | | |
450 | 547 | | |
451 | | - | |
| 548 | + | |
452 | 549 | | |
453 | 550 | | |
454 | 551 | | |
455 | 552 | | |
456 | 553 | | |
457 | 554 | | |
458 | 555 | | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
464 | | - | |
465 | | - | |
466 | | - | |
467 | | - | |
468 | 556 | | |
469 | 557 | | |
470 | 558 | | |
471 | 559 | | |
472 | 560 | | |
473 | 561 | | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
474 | 579 | | |
475 | 580 | | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
483 | | - | |
| 581 | + | |
| 582 | + | |
484 | 583 | | |
485 | | - | |
486 | | - | |
487 | | - | |
488 | | - | |
489 | | - | |
490 | | - | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
491 | 590 | | |
492 | 591 | | |
493 | 592 | | |
494 | 593 | | |
495 | | - | |
| 594 | + | |
496 | 595 | | |
497 | 596 | | |
498 | 597 | | |
| |||
503 | 602 | | |
504 | 603 | | |
505 | 604 | | |
506 | | - | |
| 605 | + | |
507 | 606 | | |
508 | 607 | | |
509 | 608 | | |
| |||
523 | 622 | | |
524 | 623 | | |
525 | 624 | | |
526 | | - | |
| 625 | + | |
527 | 626 | | |
528 | | - | |
| 627 | + | |
529 | 628 | | |
530 | 629 | | |
531 | 630 | | |
532 | | - | |
| 631 | + | |
| 632 | + | |
533 | 633 | | |
534 | | - | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
535 | 641 | | |
536 | 642 | | |
537 | 643 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
| |||
208 | 207 | | |
209 | 208 | | |
210 | 209 | | |
211 | | - | |
| 210 | + | |
212 | 211 | | |
213 | 212 | | |
214 | 213 | | |
215 | 214 | | |
216 | 215 | | |
217 | 216 | | |
218 | | - | |
| 217 | + | |
219 | 218 | | |
220 | 219 | | |
221 | | - | |
| 220 | + | |
222 | 221 | | |
223 | 222 | | |
224 | 223 | | |
| |||
0 commit comments