Sign-in users with Microsoft Personal accounts, using the Microsoft identity platform in a WPF Desktop application and call an ASP.NET Core Web API, which in turn calls Microsoft Graph

Build status

About this sample

Table of content

Scenario

In this sample, we present a pattern where a tightly-knit client and Web API share the same client id(app id) and sign-in users with Microsoft Personal Accounts. In this sample, the Web API uses the Microsoft Authentication Library for .NET (MSAL.NET) to acquire an Access Token for Microsoft Graph using the on-behalf-of flow.

The solution used in this sample is same as sample 2. Web API now calls Microsoft Graph.

Overview

This sample presents an ASP.NET core Web API, protected by Microsoft Entra ID OAuth Bearer Authorization, that also calls the Microsoft Graph on-behalf of the signed-in user. The Web API is called by a .NET Desktop WPF application.

Both applications use the Microsoft Authentication Library MSAL.NET to sign-in user and obtain a JWT access token through the OAuth 2.0 protocol.

The WPF client application:

  1. Signs-in users using the MSAL.NET library.
  2. Acquires an access token for the Web API
  3. Calls the ASP.NET Core Web API by using the access token as a bearer token in the authentication header of the Http request.

The Web API:

  1. Authorizes the caller (user) using the ASP.NET JWT Bearer Authorization middleware.
  2. Acquires another access token on-behalf-of the signed-in user using the on-behalf of flow.
  3. The Web API then uses this new Access token to call Microsoft Graph.

Topology

User experience when using this sample

The Web API (TodoListService) maintains an in-memory collection of to-do items for each authenticated user. Several applications signed-in under the same identity will share the same to-do list.

The WPF application (TodoListClient) allows a user to:

  • Sign-in. The first time a user signs in, a consent screen is presented where the user consents for the application accessing the TodoList Service on their behalf.
  • When the user has signed-in, the user is presented with a list of to-do items fetched from the Web API for this signed-in identity.
  • The user can add more to-do items by clicking on Add item button. As they add items, they see that these items appear with their user name between parenthesis.

Next time a user runs the application, the user is signed-in with the same identity as the WPF application maintains a cache on disk. Users can clear the cache (which will have the effect of them signing out).

TodoList Client

How to run this sample

Pre-requisites

  • Visual Studio 2019 or just the .NET Core SDK
  • An Internet connection
  • A Windows machine (necessary if you want to run the app on Windows)
  • An OS X machine (necessary if you want to run the app on Mac)
  • A Linux machine (necessary if you want to run the app on Linux)
  • a Microsoft Entra tenant. For more information on how to get a Microsoft Entra tenant, see How to get a Microsoft Entra tenant
  • A user account in your Microsoft Entra tenant. This sample will not work with a Microsoft account (formerly Windows Live account). Therefore, if you signed in to the Microsoft Entra admin center with a Microsoft account and have never created a user account in your directory before, you need to do that now.

Step 1: Clone or download this repository

From your shell or command line:

git clone https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2.git
cd "2. Web API now calls Microsoft Graph"

or download and extract the repository .zip file.

Given that the name of the sample is quite long, and so are the names of the referenced NuGet packages, you might want to clone it in a folder close to the root of your hard drive, to avoid file size limitations on Windows.

Step 2: Register the sample application with your Microsoft Entra tenant

There are two projects in this sample. Both needs to be bundled together for registration in your Microsoft Entra tenant.

Follow the steps below to manually walk through the steps to register and configure the applications.

Choose the Microsoft Entra tenant where you want to create your applications

As a first step you'll need to:

  1. Sign in to the Microsoft Entra admin center using either a work or school account or a personal Microsoft account.
  2. If your account is present in more than one Microsoft Entra tenant, select your profile at the top right corner in the menu on top of the page, and then switch directory. Change your portal session to the desired Microsoft Entra tenant.

Register the service app (TodoListClient-and-Service)

  1. Navigate to the Microsoft identity platform for developers App registrations page.
  2. Select New registration.
  3. In the Register an application page that appears, enter your application's registration information:
    • In the Name section, enter a meaningful application name that will be displayed to users of the app, for example TodoListClient-and-Service.
    • Under Supported account types, select Accounts in any organizational directory and personal Microsoft accounts (e.g. Skype, Xbox, Outlook.com).
  4. Select Register to create the application.
  5. In the app's registration screen, find and note the Application (client) ID. You use this value in your app's configuration file(s) later in your code.
  6. Select Save to save your changes.
  7. In the app's registration screen, click on the Certificates & secrets blade in the left to open the page where we can generate secrets and upload certificates.
  8. In the Client secrets section, click on New client secret:
    • Type a key description (for instance app secret),
    • Select one of the available key durations (In 1 year, In 2 years, or Never Expires) as per your security concerns.
    • The generated key value will be displayed when you click the Add button. Copy the generated value for use in the steps later.
    • You'll need this key later in your code's configuration files. This key value will not be displayed again, and is not retrievable by any other means, so make sure to note it from the Microsoft Entra admin center before navigating to any other screen or blade.
  9. In the app's registration screen, click on the API permissions blade in the left to open the page where we add access to the APIs that your application needs.
    • Click the Add a permission button and then:
    • Ensure that the Microsoft APIs tab is selected.
    • In the Commonly used Microsoft APIs section, click on Microsoft Graph
    • In the Delegated permissions section, select the User.Read in the list. Use the search box if necessary.
    • Click on the Add permissions button at the bottom.
  10. In the app's registration screen, select the Expose an API blade to the left to open the page where you can declare the parameters to expose this app as an API for which client applications can obtain access tokens for. The first thing that we need to do is to declare the unique resource URI that the clients will be using to obtain access tokens for this API. To declare an resource URI, follow the following steps:
    • Click Set next to the Application ID URI to generate a URI that is unique for this app.
    • For this sample, accept the proposed Application ID URI (api://{clientId}) by selecting Save.
  11. All APIs have to publish a minimum of one scope for the client's to obtain an access token successfully. To publish a scope, follow the following steps:
    • Select Add a scope button open the Add a scope screen and Enter the values as indicated below:
      • For Scope name, use access_as_user.
      • Select Admins and users options for Who can consent?
      • For Admin consent display name type Access TodoListClient-and-Service
      • For Admin consent description type Allows the app to access TodoListClient-and-Service as the signed-in user.
      • For User consent display name type Access TodoListClient-and-Service
      • For User consent description type Allow the application to access TodoListClient-and-Service on your behalf.
      • Keep State as Enabled
      • Click on the Add scope button on the bottom to save this scope.

Register the client aspect (in the same app)

In this sample, you will use the same application ID for the client part as for the service part

  1. On the app Overview page, find the Application (client) ID value and record it for later. You'll need it to configure the Visual Studio configuration file for this project (ida:ClientId in TodoListClient\App.Config).
  2. From the app's Overview page, select the Authentication section.
  3. Select the API permissions section
    • Click the Add a permission button and then,
    • Ensure that the My APIs tab is selected
    • In the list of APIs, select the TodoListClient-and-Service API, or the name you entered for the Web API.
    • In the Delegated permissions section, ensure that the right permissions are checked: access_as_user. Use the search box if necessary.
    • Select the Add permissions button

Configure the service app (TodoListClient-and-Service) to use your app registration

Open the project in your IDE (like Visual Studio) to configure the code.

In the steps below, "ClientID" is the same as "Application ID" or "AppId".

  1. Open the TodoListService\appsettings.json file
  2. Find the app key Domain and replace the existing value with your Microsoft Entra tenant name.
  3. Find the app key ClientId and replace the existing value with the application ID (clientId) of the TodoListClient-and-Service application copied from the Microsoft Entra admin center.
  4. Find the app key ClientSecret and replace the existing value with the key you saved during the creation of the TodoListClient-and-Service app, in the Microsoft Entra admin center.

Configure the client app (TodoListClient-and-Service) to use your app registration

Open the project in your IDE (like Visual Studio) to configure the code.

  1. In the TodoListClient project, open App.config.
  2. Find the app key ida:ClientId and replace the value with the ApplicationID (Client ID) for the TodoListClient-and-Service app copied from the app registration page.
  3. and replace the value with the scope of the TodoListClient-and-Service application copied from the app registration in the Expose an API tab, i.e api://{clientId}/access_as_user.
  4. [Optional] If you changed the default URL for your service application, find the app key todo:TodoListBaseAddress and replace the value with the base address of the TodoListService project.

Step 3: Run the sample

Clean the solution, rebuild the solution, and run it. You might want to go into the solution properties and set both projects as startup projects, with the service project starting first.

When you start the Web API from Visual Studio, depending on the browser you use, you'll get:

  • an empty web page (with Microsoft Edge)
  • or an error HTTP 401 (with Chrome)

This behavior is expected as the browser is not authenticated. The WPF application will be authenticated, so it will be able to access the Web API.

Explore the sample by signing in into the TodoList client, adding items to the To Do list, removing the user account (clearing the cache), and starting again. As explained, if you stop the application without removing the user account, the next time you run the application, you won't be prompted to sign in again. That is because the sample implements a persistent cache for MSAL, and remembers the tokens from the previous run.

NOTE: Remember, the To-Do list is stored in memory in this TodoListService sample. Each time you run the TodoListService API, your To-Do list will get emptied.

Consider taking a moment to share your experience with us.

How was the code created

For details about the code used for protecting a Web API, see How was the code created section, of the README.md file located in the sibling folder named 2. Web API now calls Microsoft Graph.

This section addresses the differences in the code for the Web API calling the Microsoft Graph with Microsoft personal accounts.

Changes to the client side (TodoListClient)

App.Config

There is one change in the App.Config, and one thing to check

  • The change is that the tenant should be set to common in order to let users sign-in with a personal account

    <add key="ida:Tenant" value="common"/>
    
  • The thing to draw your attention to, is that you now have the same client ID (Application ID) for the client application and the service. This is not usually the case, which is why your attention is especially drawn here.

    <add key="ida:ClientId" value="{clientId}"/>
    <add key="todo:TodoListScope" value="api://{clientId}/access_as_user"/>
    

The Web API (TodoList service) does not have the possibility of having an interaction with the user (by definition of a Web API), and therefore cannot let the user consent for the scopes it requests. Given that the Web API and the client have the same client ID, it's possible for the client to request a token for the Web API and let the user pre-consent to the scopes requested by the Web API (in this case "user.read")

This is done in MainWindow.xaml.cs in the SignIn method, by adding a modifier .WithExtraScopesToConsent(new[] { "user.read" }) to the AcquireTokenInteractive call. Replace below method:

public class MainWindow
{
 private async void SignIn(object sender = null, RoutedEventArgs args = null)
 {
  ...
  // Force a sign-in (PromptBehavior.Always), as the MSAL web browser might contain cookies for the current user, and using .Auto
  // would re-sign-in the same user
  var result = await _app.AcquireTokenInteractive(Scopes)
      .WithAccount(accounts.FirstOrDefault())
      .WithPrompt(Prompt.SelectAccount)
      .ExecuteAsync()
      .ConfigureAwait(false);
   ...
 }
}

by

public class MainWindow
{
 private async void SignIn(object sender = null, RoutedEventArgs args = null)
 {
  ...
  // Force a sign-in (PromptBehavior.Always), as the ADAL web browser might contain cookies for the current user, and using .Auto
  // would re-sign-in the same user
  var result = await _app.AcquireTokenInteractive(Scopes)
      .WithAccount(accounts.FirstOrDefault())
      .WithPrompt(Prompt.SelectAccount)
      .WithExtraScopesToConsent(new[] { "user.read" })
      .ExecuteAsync()
      .ConfigureAwait(false);
   ...
 }
}

See WithExtraScopeToConsent for more details.

Changes to the service side (TodoListService)

Startup.cs

The change is to ensure that the web API allows access to it's own client.

public void ConfigureServices(IServiceCollection services)
{      
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi(Configuration, "AzureAd")
                  .EnableTokenAcquisitionToCallDownstreamApi();
                  .AddInMemoryTokenCaches();
   ...
}

How to deploy this sample to Azure

See Readme.md to deploy this sample to Azure.

Community Help and Support

Use Stack Overflow to get support from the community. Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. Make sure that your questions or comments are tagged with [microsoft-entra-id msal dotnet].

If you find a bug in the sample, please raise the issue on GitHub Issues.

To provide a recommendation, visit the following User Voice page.

Contributing

If you'd like to contribute to this sample, see CONTRIBUTING.MD.

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

More information

For more information, visit the following links: