Home

Awesome


page_type: sample languages:


A .NET Core daemon app authenticating as itself using client credentials to call Graph and handling CAE events

  1. Overview
  2. Scenario
  3. Contents
  4. Prerequisites
  5. Setup
  6. Registration
  7. Running the sample
  8. Explore the sample
  9. About the code
  10. Deployment
  11. More information
  12. Community Help and Support
  13. Contributing

Overview

This sample application shows how to use the Microsoft identity platform endpoint to access the data of Microsoft business customers in a long-running, non-interactive process. It uses the OAuth 2 client credentials grant to acquire an access token, which it uses to then call the Microsoft Graph and access organizational data. This flow of authentication is also sometimes referred to as Service Principal (SP) authentication.

Additionally, the sample shows developers how to enable Continuous access evaluation(CAE) in a tenant and use a Conditional Access(CA) policy to enforce CAE events for this application.

Scenario

The app is a .NET Core Console application. It gets the list of users from MS Graph in an Azure AD tenant by using Microsoft Authentication Library for .NET (MSAL.NET) to acquire a token for MS Graph.

We then enable CAE for the tenant and a CA policy created/updated to apply CAE events for this application's Service Principal.

The order of processing is roughly as follows:

Topology

Prerequisites

For more information on the concepts used in this sample, be sure to read the Microsoft identity platform endpoint client credentials protocol documentation.

Setup

Step 1: Clone or download this repository

From your shell or command line:

git clone https://github.com/Azure-Samples/ms-identity-dotnetcore-daemon-graph-cae.git

or download and extract the repository .zip file.

:warning: To avoid path length limitations on Windows, we recommend cloning into a directory near the root of your drive.

Step 2: Install project dependencies

    dotnet restore

You can also use Visual Studio for the provided project.

Register the sample application(s) with your Azure Active Directory tenant

There is one project in this sample. To register it, you can:

<details> <summary>Expand this section if you want to use this automation:</summary>

:warning: If you have never used Azure AD Powershell before, we recommend you go through the App Creation Scripts once to ensure that your environment is prepared correctly for this step.

  1. On Windows, run PowerShell as Administrator and navigate to the root of the cloned directory

  2. If you have never used Azure AD Powershell before, we recommend you go through the App Creation Scripts once to ensure that your environment is prepared correctly for this step.

  3. In PowerShell run:

    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
    
  4. Run the script to create your Azure AD application and configure the code of the sample application accordingly.

  5. In PowerShell run:

    cd .\AppCreationScripts\
    .\Configure.ps1
    

    Other ways of running the scripts are described in App Creation Scripts The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios.

</details>

Choose the Azure AD tenant where you want to create your applications

As a first step you'll need to:

  1. Sign in to the Azure portal.
  2. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then switch directory to change your portal session to the desired Azure AD tenant.

Register the client app (daemon-console-cae)

  1. Navigate to the Azure portal and select the Azure AD service.

  2. Select the App Registrations blade on the left, then 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 daemon-console-cae.
    • Under Supported account types, select Accounts in this organizational directory only.
  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, select the Certificates & secrets blade in the left to open the page where you can generate secrets and upload certificates.

  8. In the Client secrets section, select New client secret:

    • Type a key description (for instance app secret),
    • Select one of the available key durations (6 months, 12 months or Custom) as per your security posture.
    • The generated key value will be displayed when you select the Add button. Copy and save the generated value for use in later steps.
    • 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 Azure portal before navigating to any other screen or blade.
  9. In the app's registration screen, select the API permissions blade in the left to open the page where we add access to the APIs that your application needs.

    • Select the Add a permission button and then,
    • Ensure that the Microsoft APIs tab is selected.
    • In the Commonly used Microsoft APIs section, select Microsoft Graph
    • In the Application permissions section, select the User.Read.All in the list. Use the search box if necessary.
    • Select the Add permissions button at the bottom.
  10. At this stage, the permissions are assigned correctly but since the client app does not allow users to interact, the users' themselves cannot consent to these permissions. To get around this problem, we'd let the tenant administrator consent on behalf of all users in the tenant. Select the Grant admin consent for {tenant} button, and then select Yes when you are asked if you want to grant consent for the requested permissions for all account in the tenant. You need to be an the tenant admin to be able to carry out this operation.

Configure the client app (daemon-console-cae) to use your app registration

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

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

  1. Open the daemon-console\appsettings.json file.
  2. Find the key Tenant and replace the existing value with your Azure AD tenant name.
  3. Find the key ClientId and replace the existing value with the application ID (clientId) of daemon-console-cae app copied from the Azure portal.
  4. Find the key ClientSecret and replace the existing value with the key you saved during the creation of daemon-console-cae copied from the Azure portal.

Running the sample

For Visual Studio Users

Clean the solution, rebuild the solution, and run it.

    cd daemon-console
    dotnet run

Start the application, it will display the users in the tenant.

Testing CAE events

Continues Access Evaluation can now be tested by adding the Service Principal of this app to a Conditional Access policy and then disabling the Service Principal via PowerShell to raise the CAE event.

Create/Update a Conditional Access policy] for this app and CAE events

  1. Go to Azure Active Directory
  2. Open Security->Conditional Access
  3. Select "New Policy" from upper menu
  4. Fill the Name field first.
  5. Under "Assignments" select "Workload Identities" from the What does this policy apply to?.
  6. Select "Select service principals" radio. You can either use "Edit filter" for advanced option or just "Select" to choose a Service Principal(s) by name

    Note: only single tenant service principals are supported by this time.

  7. Cloud apps or actions - choose "All cloud apps"
  8. Under Access Controls >Grant -> Block Access
  9. Enable policy - set it to On
  10. Press "Create" button
  11. Run daemon application and observe the terminal. After some time you will start seeing error like this: policy-blocking-error
  12. Wait for the CA policy to be created.

Declaring Client Capability

Azure AD and the CAE enabled resources, like MS Graph in this example, would not act upon CAE events unless the client (this app) declares itself to be capable of handling CAE events. This is done by sending a client capability declaration to Azure AD.

The following code is added in Program.cs , PrepareConfidentialClient.

_app = ConfidentialClientApplicationBuilder.Create(_config.ClientId)
                    .WithClientSecret(_config.ClientSecret)
                    .WithAuthority(new Uri(_config.Authority))
                    .WithClientCapabilities(new[] { "cp1" }) // Declare this app to be able to receive CAE events
                    .Build();

Once the client application announces that its capable, two changes take place:

  1. Azure AD sends an Access Token for MS Graph which is valid for 24 hours instead of the standard 1 hour.
  2. MS graph will receive CAE events and can potentially reject a valid Access token from a capable client if a CAA event occurs.

Testing CAE events by disabling Service Principal

  1. Run the application and make sure you get users graph data in continues manner. Leave the application in running state.

  2. Open PowerShell as Administrator.

  3. Use the following set of PowerShell commands to locate and disable the Service Principal of this application

    # install the Azure AD powershell if not already present
    Install-Module AzureAD
    
    # Connect to your Azure AD tenant
    Connect-AzureAD -TenantId <your tenantid>
    
    # locate the Service Principal of this app, copy its object id
    Get-AzureADServicePrincipal -SearchString "daemon-console-cae"
    
    # Disable the service principal of this app
    Set-AzureADServicePrincipal -ObjectId \<Id\> -AccountEnabled $False
    
  4. Observe the daemon application terminal and note that after some time you will start getting next error sp-disabled-error 1.You can re-test this scenario by re-enabling the Service Principal using the following command and re-starting the console app again. Please note that Service Principals, once disabled can only be enabled after 15 minutes.

    
    # Enable the service principal of this app
    Set-AzureADServicePrincipal -ObjectId \<Id\> -AccountEnabled $True
    

About the code

The relevant code for this sample is in the Program.cs file, and in the PrepareConfidentialClient() in the RunAsync() method. The steps are:

  1. Create the MSAL confidential client application.

    Important note: even if we are building a console application, it is a daemon, and therefore a confidential client application, as it does not access Web APIs on behalf of a user, but on its own application behalf.

      _app = ConfidentialClientApplicationBuilder.Create(_config.ClientId)
         .WithClientSecret(_config.ClientSecret)
         .WithAuthority(new Uri(_config.Authority))
         .WithClientCapabilities(new[] { "cp1" }) // Declare this app to be able to receive CAE events
         .Build();
    
      // Attach an app token cache
      _tokenCache = new MSALAppMemoryTokenCache(_app.AppTokenCache, _config.ClientId);
    
  2. Acquire a token and make a call to MS Graph

    Specific to client credentials, you don't specify, in the code, the individual scopes you want to access. You have to statically declare them and admin consent to them during the application registration steps as there will be no users in-front of this app to consent. Therefore the only possible scope is "resource/.default" ( https://graph.microsoft.com/.default") which means "the static permissions defined in the application"

    // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the 
    // application permissions need to be set statically (in the portal or by PowerShell), and then granted by
    // a tenant administrator
    string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
    
    AuthenticationResult result = null;
    try
    {
         // Acquire the token for MS Graph
         result = await _app.AcquireTokenForClient(scopes)
            .ExecuteAsync();
    
         Console.ForegroundColor = ConsoleColor.Green;
         Console.WriteLine("Token acquired");
         Console.ResetColor();
    }
    catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
    {
         // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
         // Mitigation: change the scope to be as expected
         Console.ForegroundColor = ConsoleColor.Red;
         Console.WriteLine("Scope provided is not supported");
         Console.ResetColor();
    }
    
    // Call MS Graph API
    if (result != null)
    {
         var httpClient = new HttpClient();
         var apiCaller = new ProtectedApiCallHelper(httpClient);
         
    
  3. Call the API

    In this case calling "https://graph.microsoft.com/v1.0/users" with the access token as a bearer token.

Troubleshooting

Did you forget to provide admin consent? This is needed for daemon apps

If you get an error when calling the API Insufficient privileges to complete the operation., this is because the tenant administrator has not granted consent to the permissions requested by the application. See the step Register the client app (daemon-console-cae) above.

You will typically see, on the output window, something like the following:

Failed to call the Web Api: Forbidden
Content: {
  "error": {
    "code": "Authorization_RequestDenied",
    "message": "Insufficient privileges to complete the operation.",
    "innerError": {
      "request-id": "<a guid>",
      "date": "<date>"
    }
  }
}

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 [msal dotnet].

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

If you find a bug in msal.Net, please raise the issue on MSAL.NET 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 about the underlying protocol:

For a more complex multi-tenant Web app daemon application, see active-directory-dotnet-daemon-v2