Tutorial: Call a protected web API from your .NET daemon app

This tutorial is the final part of a series that demonstrates how to call a protected web API from a .NET daemon app. In part 1 of this series, you prepared your external tenant to authorize a .NET daemon app. In this tutorial, you build your client daemon app and call a protected web API. You enable the client daemon app to acquire an access token using its own identity, then call the web API.

In this tutorial;

  • Configure a daemon app to use it's app registration details.
  • Build a daemon app that acquires a token on its own behalf and calls a protected web API.

Prerequisites

Create a .NET daemon app

  1. Open your terminal and navigate to the folder where you want your project to live.

  2. Initialize a .NET console app and navigate to its root folder.

    dotnet new console -n ToDoListClient
    cd ToDoListClient
    

Install packages

Install Microsoft.Identity.Web and Microsoft.Identity.Web.DownstreamApi packages:

dotnet add package Microsoft.Identity.Web
dotnet add package Microsoft.Identity.Web.DownstreamApi

Microsoft.Identity.Web provides the glue between ASP.NET Core, the authentication middleware, and the Microsoft Authentication Library (MSAL) for .NET making it easier for you to add authentication and authorization capabilities to your app. Microsoft.Identity.Web.DownstreamApi provides an interface used to call a downstream API.

Create appsettings.json file an add registration configs

  1. Create appsettings.json file in the root folder of the app.

  2. Add app registration details to the appsettings.json file.

    {
        "AzureAd": {
            "Authority": "https://<Enter_the_Tenant_Subdomain_Here>.ciamlogin.com/",
            "ClientId": "<Enter_the_Application_Id_here>",
            "ClientCredentials": [
                {
                    "SourceType": "ClientSecret",
                    "ClientSecret": "<Enter_the_Client_Secret_Here>"
                }
            ]
        },
        "DownstreamApi": {
            "BaseUrl": "<Web_API_base_url>",
            "RelativePath": "api/todolist",
            "RequestAppToken": true,
            "Scopes": [
                "api://<Enter_the_Web_Api_Application_Id_Here>/.default"
            ]
        }
    }
    

    Replace the following values with your own:

    Value Description
    Enter_the_Application_Id_Here The Application (client) ID of the client daemon app that you registered.
    Enter_the_Tenant_Subdomain_Here The Directory (tenant) subdomain.
    Enter_the_Client_Secret_Here The daemon app secret value you created.
    Enter_the_Web_Api_Application_Id_Here The Application (client) ID of the web API app you registered.
    Web_API_base_url The base URL of the web API. For example, https://localhost:44351/ where 44351 is the port number of the port your API is running on. Your API should already be running and awaiting requests by this stage for you to get this value.

Add models

Navigate to the root of your project folder and create a models folder. In the models folder, create a ToDo.cs file and add the following code:

using System;

namespace ToDoListClient.Models;

public class ToDo
{
    public int Id { get; set; }
    public Guid Owner { get; set; }
    public string Description { get; set; } = string.Empty;
}

Acquire access token

You have now configured the required items in for your daemon application. In this step, you write the code that enables the daemon app to acquire an access token.

  1. Open the program.cs file in your code editor and delete its contents.

  2. Add your packages to the file.

    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Identity.Abstractions;
    using Microsoft.Identity.Web;
    using ToDoListClient.Models;
    
  3. Create the token acquisition instance. Use the GetDefaultInstance method of the TokenAcquirerFactory class of Microsoft.Identity.Web package to build the token acquisition instance. By default, the instance reads an appsettings.json file if it exists in the same folder as the app. GetDefaultInstance also allows us to add services to the service collection.

    Add this line of code to the program.cs file:

    var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
    
  4. Configure the application options to be read from the configuration and add the DownstreamApi service. The DownstreamApi service provides an interface used to call a downstream API. We call this service DownstreamAPI in the config object. The daemon app reads the downstream API configs from the DownstreamApi section of appsettings.json. By default, you get an in-memory token cache.

    Add the following code snippet to the program.cs file:

    const string ServiceName = "DownstreamApi";
    
    tokenAcquirerFactory.Services.AddDownstreamApi(ServiceName,
        tokenAcquirerFactory.Configuration.GetSection("DownstreamApi"));
    
    
  5. Build the token acquirer. This composes all the services you have added to Services and returns a service provider. Use this service provider to get access to the API resource you have added. In this case, you added only one API resource as a downstream service that you want access to.

    Add the following code snippet to the program.cs file:

    var serviceProvider = tokenAcquirerFactory.Build();
    

Call the web API

Add code to call your protected web API using the IDownstreamApi interface. In this tutorial, you only implement a call to Post a todo and another one to Get all todos. See the other implementations such as Delete and Put in the sample code.

Add this line of code to the program.cs file:

var toDoApiClient = serviceProvider.GetRequiredService<IDownstreamApi>();

Console.WriteLine("Posting a to-do...");

var firstNewToDo = await toDoApiClient.PostForAppAsync<ToDo, ToDo>(
    ServiceName,
    new ToDo()
    {
        Owner = Guid.NewGuid(),
        Description = "Bake bread"
    });

await DisplayToDosFromServer();
    
async Task DisplayToDosFromServer()
{
    Console.WriteLine("Retrieving to-do's from server...");
    var toDos = await toDoApiClient!.GetForAppAsync<IEnumerable<ToDo>>(
        ServiceName,
        options => options.RelativePath = "/api/todolist"
    );
    
    if (!toDos!.Any())
    {
        Console.WriteLine("There are no to-do's in server");
        return;
    }
    
    Console.WriteLine("To-do data:");
    
    foreach (var toDo in toDos!) {
        DisplayToDo(toDo);
    }
}

void DisplayToDo(ToDo toDo) {
    Console.WriteLine($"ID: {toDo.Id}");
    Console.WriteLine($"User ID: {toDo.Owner}");
    Console.WriteLine($"Message: {toDo.Description}");
}

Run the client daemon app

Navigate to the root folder of the daemon app and run the following command:

dotnet run

If everything is okay, you should see the following output in your terminal.

Posting a to-do...
Retrieving to-do's from server...
To-do data:
ID: 1
User ID: 00aa00aa-bb11-cc22-dd33-44ee44ee44ee

Message: Bake bread

Troubleshoot

In case you run into errors,

  • Confirm the registration details you added to the appsettings.json file.
  • Confirm that you're calling the web API via the correct port and over https.
  • Confirm that your app permissions are configured correctly.

The full sample code is available on GitHub.

Clean up resources

If you don't intend to use the apps you have registered and created in this tutorial, delete them to avoid incurring any costs.

See also