-
Notifications
You must be signed in to change notification settings - Fork 367
SLC
Note: this is a design document and the functionality is not in any SDK.
SDK | MSI CAE support | MSI TB support | FIC CAE | FIC TB | Ease of use |
---|---|---|---|---|---|
MSAL | private preview | N | ? | N | Hard |
ID.Web | in progress work item | N | in progress work item | N | Easy |
Azure.Identity | N | N | N | N | ? |
We recommend using higher level SDKs where possible:
- Microsoft.Identity.Web
- Azure.Identity
Applications needing full control (e.g. other SDKs) can use MSAL directly.
Microsoft.Identity.Web and Azure.Identity (?) control both:
- Getting tokens
- Calling protected APIs
MSAL only controls (1) Getting Tokens. CAE and TB require auth logic to cover (2) Calling Protected APIs, such as interpreting 401 responses, parsing WWW-Authenticate headers, opening MTLS connections etc.
It is helpful to review the existing sample for performing client_credentials:
Vanilla MSI
"DownstreamApis": {
"allUsers": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"RelativePath": "/users",
"Scopes": [ "https://graph.microsoft.com/.default" ],
"AcquireTokenOptions" : {
"UseManagedIdentity": {
// No property = system-assigned
// For user-assigned set ClientID, ObjectId or HostResource
}
}
}
}
[!NOTE]
The code is identical to client_credentials, only the configuration differs
// Get the Token acquirer factory instance. By default it reads an appsettings.json
// file if it exists in the same folder as the app (make sure that the
// "Copy to Output Directory" property of the appsettings.json file is "Copy if newer").
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
// Create a downstream API service named 'MyApi' which comes loaded with several
// utility methods to make HTTP calls to the DownstreamApi configurations found
// in the "MyWebApi" section of your appsettings.json file.
tokenAcquirerFactory.Services.AddDownstreamApi("MyApi",
tokenAcquirerFactory.Configuration.GetSection("MyWebApi"));
var sp = tokenAcquirerFactory.Build();
// Extract the downstream API service from the 'tokenAcquirerFactory' service provider.
var api = sp.GetRequiredService<IDownstreamApi>();
// You can use the API service to make direct HTTP calls to your API. Token
// acquisition is handled automatically based on the configurations in your
// appsettings.json file.
var result = await api.GetForAppAsync<IEnumerable<TodoItem>>("MyApi");
Console.WriteLine($"result = {result?.Count()}");
FIC
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
"ClientId": "56c9a633-236e-45ee-9af1-a53d9811fbd6",
"ClientCredentials": [
{
"SourceType": "SignedAssertionFromManagedIdentity",
"ManagedIdentityClientId": "02c0b640-8e3d-405e-999d-4781f2f0438a" # ignore for System Assigned
}]
},
[!NOTE]
The code is identical to the client_credentials code / MSI code above. It's all config driven.
// Get the Token acquirer factory instance. By default it reads an appsettings.json
// file if it exists in the same folder as the app (make sure that the
// "Copy to Output Directory" property of the appsettings.json file is "Copy if newer").
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
// Create a downstream API service named 'MyApi' which comes loaded with several
// utility methods to make HTTP calls to the DownstreamApi configurations found
// in the "MyWebApi" section of your appsettings.json file.
tokenAcquirerFactory.Services.AddDownstreamApi("MyApi",
tokenAcquirerFactory.Configuration.GetSection("MyWebApi"));
var sp = tokenAcquirerFactory.Build();
// Extract the downstream API service from the 'tokenAcquirerFactory' service provider.
var api = sp.GetRequiredService<IDownstreamApi>();
// You can use the API service to make direct HTTP calls to your API. Token
// acquisition is handled automatically based on the configurations in your
// appsettings.json file.
var result = await api.GetForAppAsync<IEnumerable<TodoItem>>("MyApi");
Console.WriteLine($"result = {result?.Count()}");
Vanilla MSI
public async AuthenticationResult GetMsiTokenAsync(string resource, string claims )
{
ManagedIdentityApplication mia = ManagedIdentityApplication
.Create(ManagedIdentityType.SystemAssigned)
.WithClientCapabilities(new[] {"cp1"} ); // NEW: claims support for MSI
// if claims are not null, cache is bypassed and a new token is acquired.
var authResult = await mia.AcquireToken(resource)
// NEW: claims support
.WithClaims(claims)
// NEW: MTLS POP support (if EITHER client or resource don't support MTLS, Bearer token will be issued)
.TryMtlsProofOfPosession()
.ExecuteAsync();
// App may choose to monitor the following:
// Claim challenges are only supported when new IMDS SLC endpoint is deployed (TBD on key provisioning)
Metrics.Log("cae_support", mia.IsClaimsChallengeSupported());
// MTLS is supported by the client if IMDS SLC endpoint is deployed and a KeyGuard key is found (TBD on key provisioning)
Metrics.Log("mtls_client_support", mia.IsMtlsSupportedByClient());
// For POP token to be issued, both client and RP must provide support
Metrics.Log("token_type", authResult.TokenType); // Bearer or MTLS-POP (?)
return authResult;
}
public async HttpResponse CallProtectedApiAsync(AuthenticationResult ar, HttpRequest request)
{
// 1. Add auth header which can be "Bearer eyXYZ" or "MTLS? eyXYZ"
request.Headers.Authorization.Add(ar.GetAuthorizationHeader());
// 2. Create HTTP client with client MTLS cert to talk to resource
var httpClient = GetHttpClient(ar.MtlsCertificate);
HttpResponse response = await s_httpClient.SendAsync(request);
// 3. If response is 401, perform the CAE dance
if (response.StatusCode == 401)
{
// parse the WWWAuthenticate header and look for claim challenge
string claims = WWWAuthenticateHeaders.Parse(response);
if (claims != null)
{
AuthenticationResult arWithClaims = GetMsiTokenAsync(claims);
// TODO: break the recursion loop after 1-2 attempts
return CallProtectedApi(arWithClaims);
}
}
return response;
}
private static ConcurrentDictionary<string, HttpClient> s_httpClients =
new ConcurrentDictionary<string, HttpClient>();
public static HttpClient GetHttpClient(X509Certificate2 certificate)
{
if (certificate=null) {
return s_httpClients.GetOrAdd("non_mtls", (cert) => return new HttpClient());
}
return s_httpClients.GetOrAdd(cert.Thumbprint, (cert) => return new HttpClient() { ClientCertificate = cert);
}
// ORCHESTRATION LOGIC
var ar = await GetMsiTokenAsync("https://arm.management.com/.default", claims: null);
HttpRequest request = new HttpRequest("https://arm.management.com/list_stuff");
HttpResponse response = await CallProtectedApiAsync(ar, request);
- What is the Authenticate header scheme for mtls? I don't think it can be POP as it will conflict with SHR POP.
- Will there be a separate MTLS endpoint on each resource, similar to ESTS endpoint?
// Leg1 - get MSI token var ar = await GetMsiTokenAsync("https://arm.management.com/.default");
- Home
- Why use MSAL.NET
- Is MSAL.NET right for me
- Scenarios
- Register your app with AAD
- Client applications
- Acquiring tokens
- MSAL samples
- Known Issues
- Acquiring a token for the app
- Acquiring a token on behalf of a user in Web APIs
- Acquiring a token by authorization code in Web Apps
- AcquireTokenInteractive
- WAM - the Windows broker
- .NET Core
- Maui Docs
- Custom Browser
- Applying an AAD B2C policy
- Integrated Windows Authentication for domain or AAD joined machines
- Username / Password
- Device Code Flow for devices without a Web browser
- ADFS support
- High Availability
- Regional
- Token cache serialization
- Logging
- Exceptions in MSAL
- Provide your own Httpclient and proxy
- Extensibility Points
- Clearing the cache
- Client Credentials Multi-Tenant guidance
- Performance perspectives
- Differences between ADAL.NET and MSAL.NET Apps
- PowerShell support
- Testing apps that use MSAL
- Experimental Features
- Proof of Possession (PoP) tokens
- Using in Azure functions
- Extract info from WWW-Authenticate headers
- SPA Authorization Code