Create a .NET Core Deamon app that calls MSGraph with a certificate

A couple of days ago I blogged about pulling OneDrive data with MS Graph in .NET Core. I wrote all the code in a console app because it was the simplest way to get me what I needed. Mind you, a console app is not the best way when it comes to creating user interactive apps but it's convenient. However, convenience comes with a price: I ended up writing a console app that needs user interaction for authenticating the app. So what is the right way to do this? How can we create a headless app/daemon that can still call into MS Graph securely and efficiently?

With a bit of research I found what I needed. Azure AD allows apps to run without user interaction using the Client Credential flow. To achieve this, we need to register an app in Azure AD and configure either a Client Secret or a Certificate that we can use to authenticate and query MS Graph.

There are 3 distinct components in this project:

  1. Create the certificate to use for authentication
  2. Register the app in Azure AD and configure the appropriate API Permissions
  3. Implement the code to do the magic

Create a self-signed certificate

As I mentioned earlier, the Client Credentials flow allows us to speak with Azure AD using a Client ID and either a Client Secret or a certificate. The tricky bit with the Client Secret is storing and managing the plain-text value securely. Developers tend to focus on building things and security is regularly overlooked. Therefore, it shouldn't come as a surprise that we find client secrets scattered in places that they should never be like: checked in source code, environment variables or config files. This can significantly compromise the security of your data. Certificates, on the other hand, tend to be managed better by enterprises so they should be preferred over Client Secrets.

Not all is lost for Client Secrets though. For example, we could use a combination of Managed Identities, Azure AD and Azure Key Vault to securely store and retrieve Client Secrets without compromising the integrity and security of your app. I wrote before how to achieve this tight security loop so feel free to take a look if you still want to go down this route.

Now let's get back to the task at hand. We need a certificate! If you already have one, you're sorted. Just follow the commands below to export it - assuming you have the certificate thumbprint at hand (looks like this: 483219ae06a1f8a85b1e500b94575559feab3f3b)

$cert = Get-ChildItem -Path Cert:\CurrentUser\My\<Certificate Thumbprint>
Export-Certificate -Cert $cert -FilePath c:\users\cmatskas\certWithKey.cer

If you don't have a certificate, we can create one using PowerShell before exporting for use in the AAD portal.

$cert=New-SelfSignedCertificate -Subject "CN=ConsoleAppCert" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable
Export-Certificate -Cert $cert -FilePath c:\users\cmatskas\certWithKey_2.cer

Note that we need to export the key as a *.cer as this is the format expected by Azure AD. Also, we are using the Export-Certificate cmdlet instead of the Export-PfxCertificate one because we don't need to export the private key.

We now have a certificate in the personal store and exported, ready to be configured in our Azure AD app, which is the next step.

Create and configure the Azure AD App

Our daemon will use an Azure AD app to authenticate and retrieve the appropriate permissions to call into the Graph API. Head to the Azure AD Portal > App Registrations and click on the New Registration:

Give it a meaningful name, select accounts in my Org only and click on Register. Leave all other options empty.

Next, we need to navigate to the Certificates & Secrets section to add our exported certificate.

Finally, we need to configure the API permissions. Because, unlike my previous examples, in this case we know that there will be no user to authenticate against Azure AD at runtime. Therefore, we need to declare in advance and approve the app permissions to ensure that there are no access issues. Head over to API Permissions, click on Add a permission

Select Microsfot Graph, choose Application Permissions and search for the Users.Read.All permission. Make sure to click on the Add permissions at the bottom of the page.

Almost there, one more small, but very important, step. Our app registration now has a certificate for authentication and the necessary permissions to read users' data from the directory. However, if we were to try to retrieve the data now, (I use Postman to test Graph calls) we would get a 403 error message like this

{
  "error": {
    "code": "Authorization_RequestDenied",
    "message": "Insufficient privileges to complete the operation.",
    "innerError": {
      "request-id": "d1b49a31-dd42-415b-b36e-3c2959881411",
      "date": "2020-05-07T21:57:57"
    }
  }
}

That's because the app permissions need to be granted consent by an admin. Let's go ahead and do this (if you don't have admin permissions, you will need to ask your AD admin to consent these for you). In the API Permissions tab, press the Grant admin consent for <your tenant name>

Now we are good to rock 'n roll!

Create the daemon app

For this example I decided to use a .NET Core console app to be my headless "daemon" that grabs a list of all the users in my test AD tenant. Although the example is fairly basic, once you wire up all the components, the full Graph API functionality is at your disposal to implement the solution that meets your needs.

In Visual Studio 2019, create a new console app that targets .NET Core 3.1. Next we need to add the necessary NuGet packages for Graph, Auth and config settings:

  • Microsoft.Graph.Beta
  • Microsoft.Identity.Client
  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Binder
  • Microsoft.Extensions.Configuration.Json

Add an appsettings.json file and populate the appropriate values

  "Scopes": "https://graph.microsoft.com/.default",
  "Tenant": "<Your Tenant Id>",
  "ClientId": "<Your Client Id>",
  "CertificateName": "CN=ConsoleAppCert"

The CertificateName value should be different as it's likely that the name of your certificate is different, unless you followed my instructions verbatim earlier.

Unlike in apps with UI where the users are expected to log in and consent to permissions, when we create daemon apps the permisions have to be pre-declared and pre-approved for the application to work . However, we still have to provide a scope. The scope for MS Graph is always https://graph.microsoft.com/.default

Next, add a new class named AuthenticationConfig.cs and add the following code:

 public class AuthenticationConfig
    {
        public string Scopes { get; set; } = "https://graph.microsoft.com/.default";
        public string Tenant { get; set; }
        public string ClientId { get; set; }
        public string CertificateName { get; set; }
    }

We now need to add a new class to handle the authentication process for us. Create a new class ClientCredentialsAuthProvider and add the following code:

This class gets the authentication token. It also retrieves the necessary certificate so that we can securely identify against Azure AD.

For the next step, we are adding an MS Graph helper class to do the necessary call(s) to the Graph API. Create a new class GraphHelper.cs and add the following code:

Finally, we are going to wire everything up in the Program.cs. The final code shoud look like this:

Let's run the app and see the magic happen:

The code successfully authenticates in Azure AD and then pulls all the users in my test tenant. It's great to see everything working as expected, without any user interaction.

If you get an error like the one below, make sure that you have added and consented to the appropriate Graph API permissions in the Azure AD App Registration:

Conclusion

If you need to create a headless/daemon app that calls into MS Graph, there are a couple of steps that you need to follow in order to get everything wired up. You need

  • a certificate to avoid storing client secrets insecurely,
  • an app registration in Azure AD with pre-declared and pre-consented permissions
  • a few lines of code to put all of it together

I hope that this blog helps you get started with Azure AD and MS Graph but feel free to ping me if you have any questions.