Skip to content
Bogdan Gavril edited this page Nov 2, 2023 · 13 revisions

Which SDKs support the new MSI features ?

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 ?

When to use which SDK?

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.

How do higher level APIs help?

Microsoft.Identity.Web and Azure.Identity (?) control both:

  1. Getting tokens
  2. 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.

Id.Web Experience

It is helpful to review the existing sample for performing client_credentials:

Vanilla MSI

Configuration

 "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
               }
             }
         }
       }

Code

[!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

Config

{

 "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()}");

MSAL experience

Vanilla MSI
public async AuthenticationResult GetMsiTokenAsync(string resource, string claims )
{
    ManagedIdentityApplication mia = ManagedIdentityApplication
                                         .Create(ManagedIdentityType.SystemAssigned)
                                         .WithClientCapabilities(new[] {"cp1"} );

    // if claims are not null, cache is bypassed and a new token is acquired.
    var authResult = await mia.AcquireToken(resource)
                              .WithClaims(claims) 
                              .TryMtlsProofOfPosession() // if resource doesn't support it, we'll still get a Bearer
                              .ExecuteAsync();

    return authResult;
}


public async HttpResponse CallProtectedApiAsync(AuthenticationResult ar, HttpRequest request)
{
    request.Headers.Authorization.Add(ar.GetAuthorizationHeader());
    var httpClient = GetHttpClient(ar.MtlsCertificate);
    HttpResponse response = await s_httpClient.SendAsync(request);

    if (response.StatusCode == 401)
    { 
        string claims = ParseClaimsChallenge(response);
        if (claims != null) 
        {
            AuthenticationResult arWithClaims = GetMsiTokenAsync(claims);
            return CallProtectedApi(arWithClaims);  // TODO: break the recursion loop after 1-2 attempts
        }
    }
 
    return response;
    
}

string ParseClaimsChallenge(HttpResponse response)
{
    var params = WWWAuthenticeHeaders.Parse(response);
    return params.Claims;
}

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);

FIC + CAE + TB

// Leg1 - get MSI token var ar = await GetMsiTokenAsync("https://arm.management.com/.default");

Getting started with MSAL.NET

Acquiring tokens

Web Apps / Web APIs / daemon apps

Desktop/Mobile apps

Advanced topics

FAQ

Other resources

Clone this wiki locally