Skip to content
Jean-Marc Prieur edited this page Sep 15, 2022 · 42 revisions

Microsoft.Identity.Web v2.0-preview

One API for all .NET platforms and all confidential client scenarios

Whether you are building an ASP.NET or OWIN web app or web API, or a daemon application running on .NET Framework, or .NET Core, you can now use the same interface to acquire tokens to call (downstream) web APIs:

  • GraphServiceClient to call Microsoft graph
  • IDownstreamRestApi to call a REST downstream API other than Microsoft Graph, having Microsoft Identity Web taking care of the details about authentication tokens and protocols.
  • IAuthorizationHeaderProvider to compute the authorization header to call a downstream API, if you can't use IDownstreamRestApi.
  • ITokenAcquirer to acquire a token, if you need to use an SDK that needs a token (usually this SDK would only support bearer tokens)

You can use one configuration for all the platforms, in JSON, and inspired from the ASP.NET Core configuration.

Common configuration

{
 "AzureAd": {
  "Instance": "https://login.microsoftonline.com",
  "TenantId": "common",
  "ClientId": "GUID", // from app registration
  "EnablePiiLogging": false, // set to true to enable PII

  // Computed. but overridable (and also to support other IdPs than AAD)
  "Authority": "https://login.microsoftonline.com/common/v2.0",

  // If the app calls downstream APIs
  "ClientCredentials": [
   {
    "SourceType": "SignedAssertionFromManagedIdentity",
    "ManagedIdentityClientId": "GUID of user assigned Managed identity"
   },
   {
    "SourceType": "KeyVault",
    "KeyVaultUrl": "https://webappsapistests.vault.azure.net",
    "KeyVaultCertificateName": "Self-Signed-5-5-22"
   },
   {
    "SourceType": "ClientSecret",
    "ClientSecret": "***"
   }
  ],
  "SendX5C": false, // Can we omit?

  // If the app is a web API
  "TokenDecryptionCredentials": [
   {
    "SourceType": "KeyVault",
    "KeyVaultUrl": "https://webappsapistests.vault.azure.net",
    "KeyVaultCertificateName": "Self-Signed-5-5-22"
   }
  ],
  "AllowWebApiToBeAuthorizedByACL": false,

  // If the app is a web app:
  "ResetPasswordPath": "/MicrosoftIdentity/Account/ResetPassword",
  "ErrorPath": "/MicrosoftIdentity/Account/Error",
  "WithSpaAuthCode": false,

  "Audience": "GUID",
  "Audiences": [],
  "AzureRegion" : "TryAutoDetect",

  // B2C Specific
  "Domain": "microsoft.com",
  "EditProfilePolicyId": null,
  "SignUpSignInPolicyId": null,
  "ResetPasswordPolicyId": null,
  "DefaultUserFlow": null
 },

 // Downstream APIs
 "DownstreamApis": [
  {
   "GraphBeta": {
    "BaseUrl": "https://graph.microsoft.com/beta",
    "Scopes": "user.read"
   }
  }
 ],


 "Logging": {

 }
}

ASP.NET Core

For ASP.NET Core, see web apps and web APIs

OWIN

IAppBuilder now has extension methods:

  • AddMicrosoftIdentityWebApp (which supports guest accounts)
  • AddMicrosoftIdentityWebApi

From the Controllers, it's possible to get an instance of GraphServiceClient, ITokenAcquirer, and IAuthorizationHeaderProvider

OWIN Web app

The full code is available from tests\aspnet-mvc\OwinWebApp

The Startup.auth.cs can now be:

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;

namespace OwinWebApp
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.AddMicrosoftIdentityWebApp(configureServices: services =>
            {
                services.Configure<ConfidentialClientApplicationOptions>(options => 
                 { options.RedirectUri = "https://localhost:44386/"; });
                services.AddMicrosoftGraph();
                services.AddInMemoryTokenCaches();
            });
                 
        }
    }
}

OWIN Web API

The full code is available from tests\aspnet-mvc\OwinWebApi

using Owin;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Logging;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using System.Diagnostics.Tracing;
using System.IO;
using System;

namespace OwinWebApi
{
    public partial class Startup
    {
        // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            app.AddMicrosoftIdentityWebApi(configureServices: services =>
            {
                services.AddMicrosoftGraph();
                services.AddInMemoryTokenCaches();
            });
           
        }
    }
}

OWIN Controllers

   [Authorize]
    public class TodoListController : ApiController
    {
        private static readonly Dictionary<int, Todo> todoStore = new Dictionary<int, Todo>();

        // GET api/values
        public async Task<IEnumerable<Todo>> Get()
        {
            // Example calling Graph
            GraphServiceClient? graphServiceClient = this.GetGraphServiceClient();
            var me = await graphServiceClient?.Me?.Request()?.GetAsync();

            // Example getting a token to call a downstream web API
            ITokenAcquirer tokenAcquirer = this.GetTokenAcquirer();
            var result = await tokenAcquirer.GetTokenForUserAsync(new[] { "user.read" });

Daemon app

The full code is available from tests/daemon-app/Daemon-app/Program.cs

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;

namespace daemon_console
{
    /// <summary>
    /// This sample shows how to query the Microsoft Graph from a daemon application
    /// which uses application permissions.
    /// For more information see https://aka.ms/msal-net-client-credentials
    /// </summary>
    class Program
    {
        static async Task Main(string[] args)
        {
            TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
            IConfiguration configuration = tokenAcquirerFactory.Configuration;
            IServiceCollection services = tokenAcquirerFactory.Services;

            services.Configure<MicrosoftAuthenticationOptions>(option => configuration.Bind(option));
            services.AddMicrosoftGraph(); // or services.AddTokenAcquisition() if you don't need graph

            // Add a cache
            services.AddDistributedTokenCaches();

            var serviceProvider = tokenAcquirerFactory.Build();

            // Example of usage of Microsoft Graph
            GraphServiceClient graphServiceClient = serviceProvider.GetRequiredService<GraphServiceClient>();
            var users = await graphServiceClient.Users
                .Request()
                .WithAppOnly()
                .WithAuthenticationOptions(options => options.ProtocolScheme = "Bearer")
                .GetAsync();

            Console.WriteLine($"{users.Count} users");
            // Get the authorization request creator service
            IAuthorizationHeaderProvider authorizationHeaderProvider = serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();
            string authorizationHeader = await authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync("https://graph.microsoft.com/.default");
            Console.WriteLine(authorizationHeader.Substring(0, authorizationHeader.IndexOf(" ")+4)+"...");

            // Example of getting a token to call a dowstream web API
            ITokenAcquirer tokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer();
            var result = await tokenAcquirer.GetTokenForAppAsync("https://graph.microsoft.com/.default");
            Console.WriteLine($"Token expires on {result.ExpiresOn}");
        }
    }
}

You now control everything when calling MSGraph.

When you use the Graph, you can now augment the request with WithAuthenticationOptions, which provides you with all the control you need.

       var users = await graphServiceClient.Users
                .Request()
                .WithAppOnly()
                .WithAuthenticationOptions(options => options.ProtocolScheme = "Bearer")
                .GetAsync();

image

Simplified configuration.

In the past, Microsoft.Identity.Web was exposing the MicrosoftAuthenticationOptions, but this was:

  • only on ASP.NET Core
  • and it inherited from OpenIdConnectOptions.

In v2.0, Microsoft.Identity.Web proposes the notion of MicrosoftAuthenticationOptions (specific to AAD), inheriting from AuthenticationOptions (standard compliant), and makes these available in OWIN and SDK/daemon scenarios as well as in ASP.NET Core

Performance is greatly improved

Between x3 and x6 for an end to end call to web API calling graph.

Credentials are generalizing certificates.

In Microsoft.Identity.Web 1.x, you could specify the ClientCertificates, in many different ways.

From Microsoft.Identity.Web 2.x, you can now specify ClientCredentials. In addition to certificates, (and client secrets), you'll be able to specify signed assertions generated by Managed identity, for instance when the app is hosted in Kubernates, leveraging the identity federation for Kubernates based on PodIdentity.

Microsoft.Identity.Web will try the credentials in the order you specify them, which means you can try things out locally, or deployed, seamlessly.

More credentials will be available as Azure AD supports them.

You can also now provide the UserAssignedManagedIdentityClientId per credential, instead of providing it statically for the application.

Certificate loader now enables you to add your own loaders.

If you have specific ways to load your certificates, not supported by Microsoft.Identity.Web, you can add your own credential loader to the _credentialLoaders property of DefaultCertificateLoader. For this, implement the ICredentialLoader interface, and assign a value other than the enumerations in CredentialSource.

 /// <summary>
    /// Interface to implement loading of credentials.
    /// </summary>
    public interface ICredentialLoader
    {
        /// <summary>
        /// Load the credential from the description, if needed.
        /// </summary>
        /// <param name="credentialDescription">Description of the credential.</param>
        void LoadIfNeeded(CredentialDescription credentialDescription);

        /// <summary>
        /// Loadable CredentialSource.
        /// </summary>
        CredentialSource CredentialSource { get; }
    }

IDownstreamRestApi

ITokenAcquirer, ITokenAcquirerFactory, AcquireTokenResult, etc ...

Why is it a v2.0?

  • The code is distributed in different assemblies
  • You won't have any breaking change if you were already doing the right thing with the authentication schemes (that is when you configure options you are already specifying an authentication scheme)

Getting started with Microsoft Identity Web

Token cache serialization

Web apps

Web APIs

Daemon scenario

Advanced topics

FAQ

News

Contribute

Other resources

Clone this wiki locally