|
| 1 | +# MSAL Cache Key Extensibility Proposal |
| 2 | + |
| 3 | +This document looks at defining a strategy for extending how tokens are cached internally by MSAL, allowing app developers to cache multiple tokens by a custom key. |
| 4 | + |
| 5 | +## Functional Requirements |
| 6 | + |
| 7 | +- Allow higher level SDKs to extend the default cache key semantics that all MSALs come with. |
| 8 | +- Non-breaking changes |
| 9 | +- Implementations MUST be consistent across confidential client MSALs |
| 10 | +- `AcquireTokenForClient` (client_credentials) MUST support this mechanism. Other confidential client flows (web app, web api, ROPC) SHOULD support it. |
| 11 | + |
| 12 | +## Non-Functional Requirements |
| 13 | + |
| 14 | +- Performance of cache look-up operations does not degrade. |
| 15 | +- Forward compatibility strategy - older MSALs must continue to work on the with the same cache as the new ones, to support upgrade scenarios. If this is not possible, an intermediate version of MSAL must be released that supports this. |
| 16 | +- Logging must be extended so that MSAL developers can understand a cache miss. |
| 17 | + |
| 18 | +### Example scenarios that will use this extensibility point |
| 19 | + |
| 20 | +- Associate tokens with the client certificate used to obtain them. |
| 21 | +- Associtate tokens with the client secrets used to obtain them. |
| 22 | +- Allow an application to associate tokens with a SPIFEE type of identifier which is known upfront by the clients. |
| 23 | + |
| 24 | +### Prior art |
| 25 | + |
| 26 | +The cache key schema is defined [here](https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=/SSO/Schema.md) and extended for POP tokens [here](https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=/SSO/change_proposals/11232019-accesstoken_with_authscheme.md). A related cache extensiblity enhancement has been done [here](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4922). |
| 27 | + |
| 28 | +## Developer Experience |
| 29 | + |
| 30 | +#### MSAL |
| 31 | + |
| 32 | +```csharp |
| 33 | + |
| 34 | +var app = ConfidentialClientApplicationBuilder.Create("client_id") |
| 35 | + .WithClientCertifiacte(x509Cert) |
| 36 | + .WithExperimentalFeatures(true) // All extensiblity APIs remain experimental |
| 37 | + .BuildConcrete(); |
| 38 | + |
| 39 | +var result = await app.AcquireTokenForClient("https://graph.microsoft.com/.default") |
| 40 | + .WithAdditionalCacheKeyComponents(new Dictionary<string, string>{ // New API |
| 41 | + {"cert_thubprint", x509Cert.GetSha2THumbprint() }, |
| 42 | + {"spiffee_id", "37" } |
| 43 | + }); |
| 44 | + .ExecuteAsync(); |
| 45 | +``` |
| 46 | + |
| 47 | +After executing the AcquireToken instruction, MSAL shall associate the token with the existing components - authority, scope and client_id. In addition, it will assocaite the token with "cert_thumbprint" and "spifee_id". |
| 48 | + |
| 49 | +#### Higher level APIs |
| 50 | + |
| 51 | +Do not expose this logic in higher level APIs. |
| 52 | + |
| 53 | +## Token Schema changes |
| 54 | + |
| 55 | +### Cache Key |
| 56 | + |
| 57 | +Similar to the [POP](https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=/SSO/change_proposals/11232019-accesstoken_with_authscheme.md&_a=preview) enhancement, a new credential type will be used - `atext`. |
| 58 | + |
| 59 | +The access token cache key will also add a suffix composed as follows (all operations are ordinal case sensitive): |
| 60 | + |
| 61 | +1. Take the key-value pair list of components and **order** it alphabetically by the key (e.g. "key1": "val1", "key2": "val2") |
| 62 | +1. Concatenate this list (e.g. "key1val1key2val2") |
| 63 | +1. Hash this using SHA256 - (e.g. `cc252f65706f969930208e4b2403435a95f7f7d9c964bd190ae2c6e032938235`) |
| 64 | + |
| 65 | +So for the token in the example above the cache entry will be: |
| 66 | + |
| 67 | +`-login.microsoftonline.com-atext-client_id-tenant_id-https://graph.microsoft.com/.deafult-cc252f65706f969930208e4b2403435a95f7f7d9c964bd190ae2c6e032938235` |
| 68 | + |
| 69 | +### Cache payload |
| 70 | + |
| 71 | +Each key value pair must be included in the cache payload. Avoid collisions with existing documented keys. |
| 72 | + |
| 73 | +Example: |
| 74 | + |
| 75 | +```json |
| 76 | +{ |
| 77 | + "AccessToken": { |
| 78 | + "-login.microsoftonline.com-atext-client_id-tenant_id-https://graph.microsoft.com/.deafult-cc252f65706f969930208e4b2403435a95f7f7d9c964bd190ae2c6e032938235": { |
| 79 | + "home_account_id": "6afc833f-49c0-4fd5-b685-2998a6cc8d8d.469fdeb4-d4fd-4fde-991e-308a78e4bea4", |
| 80 | + "environment": "login.microsoftonline.de", |
| 81 | + "client_id": "0615b6ca-88d4-4884-8729-b178178f7c27", |
| 82 | + "secret": "omitted", |
| 83 | + "credential_type": "atext", // new! |
| 84 | + "realm": "469fdeb4-d4fd-4fde-991e-308a78e4bea4", |
| 85 | + "target": "https://graph.cloudapi.de/62e90394-69f5-4237-9190-012177145e10 https://graph.cloudapi.de/.default", |
| 86 | + "cached_at": "1553819803", |
| 87 | + "expires_on": "1553823402", |
| 88 | + "key1": "val1", // new! |
| 89 | + "key2": "val2", // new! } |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +Note: in case the values contain reserved JSON characters, use standard JSON escape rules to serialize. |
| 95 | + |
| 96 | +### Cache lookup logic |
| 97 | + |
| 98 | +Both the hash value and the actual payload values must be taken into account when performing cache lookups. |
| 99 | + |
| 100 | +### External cache key |
| 101 | + |
| 102 | +MSALs which suggest a distributed cache key must include this differentiator in the cache key |
| 103 | + |
| 104 | +### Interop with POP key |
| 105 | + |
| 106 | +Leave the keyID logic as is. We can come back to this. |
| 107 | + |
| 108 | +### User token cache key |
| 109 | + |
| 110 | +In user flows, only the access tokens will be cached by the new schema. Refresh tokens and Id tokens remain shared. For simplicity, do **not** extend this API to user flows until a few scenarios crop up! |
| 111 | + |
| 112 | +## Acceptance tests |
| 113 | + |
| 114 | +1. The following tests assume a call to `AcquireTokenForClient` with `WithAdditionalCacheKeyComponents` set to "key1"="val1", "key2"="val2". Always assert suggested cache key. |
| 115 | + |
| 116 | +- Call `AcquireTokenForClient` with the same "key1"="val1", "key2"="val2". Assert cache hit. |
| 117 | +- Call `AcquireTokenForClient` with no extensibility. Assert cache miss. |
| 118 | +- Call `AcquireTokenForClient` with the same "key1"="val1". Assert cache miss. |
| 119 | +- Call `AcquireTokenForClient` with the same "key1"="val1", "key2"="foo". Assert cache miss. |
| 120 | +- Call `AcquireTokenForClient` with the same "Key1"="val1", "key2"="val2". Assert cache miss (capital "K" used in "key1") |
| 121 | + |
| 122 | +2. Forwards compatibility test: old MSAL must function side by side with new MSAL, i.e. old MSAL must ignore the new access token cache entries. |
| 123 | + |
| 124 | +3. Try to use `WithAdditionalCacheKeyComponents` set to `client_id` -> error |
| 125 | +4. POP Access token (all known schemes) and WithAdditionalCacheKeyComponents. Assert external cache key. |
0 commit comments