Edit

Share via


Authenticate C++ apps to Azure services during local development using service principals

During local development, applications need to authenticate to Azure to access various Azure services. Two common approaches for local authentication are to use a developer account or a service principal. This article explains how to use an application service principal. In the sections ahead, you learn:

  • How to register an application with Microsoft Entra to create a service principal
  • How to use Microsoft Entra groups to efficiently manage permissions
  • How to assign roles to scope permissions
  • How to authenticate using a service principal from your app code

Using dedicated application service principals allows you to adhere to the principle of least privilege when accessing Azure resources. Permissions are limited to the specific requirements of the app during development, preventing accidental access to Azure resources intended for other apps or services. This approach also helps avoid issues when the app is moved to production by ensuring it isn't over-privileged in the development environment.

A diagram showing how a local C++ app uses a service principal to connect to Azure resources.

When the app is registered in Azure, an application service principal is created. For local development:

  • Create a separate app registration for each developer working on the app to ensure each developer has their own application service principal, avoiding the need to share credentials.
  • Create a separate app registration for each app to limit the app's permissions to only what is necessary.

During local development, environment variables are set with the application service principal's identity. The Azure Identity library reads these environment variables to authenticate the app to the required Azure resources.

Register the app in Azure

Application service principal objects are created through an app registration in Azure using either the Azure portal or Azure CLI.

  1. In the Azure portal, use the search bar to navigate to the App registrations page.

  2. On the App registrations page, select + New registration.

  3. On the Register an application page:

    • For the Name field, enter a descriptive value that includes the app name and the target environment.
    • For the Supported account types, select Accounts in this organizational directory only (Microsoft Customer Led only - Single tenant), or whichever option best fits your requirements.
  4. Select Register to register your app and create the service principal.

    A screenshot showing how to create an app registration in the Azure portal.

  5. On the App registration page for your app, copy the Application (client) ID and Directory (tenant) ID and paste them in a temporary location for later use in your app code configurations.

  6. Select Add a certificate or secret to set up credentials for your app.

  7. On the Certificates & secrets page, select + New client secret.

  8. In the Add a client secret flyout panel that opens:

    • For the Description, enter a value of Current.
    • For the Expires value, leave the default recommended value of 180 days.
    • Select Add to add the secret.
  9. On the Certificates & secrets page, copy the Value property of the client secret for use in a future step.

    Note

    The client secret value is only displayed once after the app registration is created. You can add more client secrets without invalidating this client secret, but there's no way to display this value again.

Create a Microsoft Entra group for local development

Create a Microsoft Entra group to encapsulate the roles (permissions) the app needs in local development rather than assigning the roles to individual service principal objects. This approach offers the following advantages:

  • Every developer has the same roles assigned at the group level.
  • If a new role is needed for the app, it only needs to be added to the group for the app.
  • If a new developer joins the team, a new application service principal is created for the developer and added to the group, ensuring the developer has the right permissions to work on the app.
  1. Navigate to the Microsoft Entra ID overview page in the Azure portal.

  2. Select All groups from the left-hand menu.

  3. On the Groups page, select New group.

  4. On the New group page, fill out the following form fields:

    • Group type: Select Security.
    • Group name: Enter a name for the group that includes a reference to the app or environment name.
    • Group description: Enter a description that explains the purpose of the group.

    A screenshot showing how to create a group in the Azure portal.

  5. Select the No members selected link under Members to add members to the group.

  6. In the flyout panel that opens, search for the service principal you created earlier and select it from the filtered results. Choose the Select button at the bottom of the panel to confirm your selection.

  7. Select Create at the bottom of the New group page to create the group and return to the All groups page. If you don't see the new group listed, wait a moment and refresh the page.

Assign roles to the group

Next, determine what roles (permissions) your app needs on what resources and assign those roles to the Microsoft Entra group you created. Groups can be assigned a role at the resource, resource group, or subscription scope. This example shows how to assign roles at the resource group scope, since most apps group all their Azure resources into a single resource group.

  1. In the Azure portal, navigate to the Overview page of the resource group that contains your app.

  2. Select Access control (IAM) from the left navigation.

  3. On the Access control (IAM) page, select + Add and then choose Add role assignment from the drop-down menu. The Add role assignment page provides several tabs to configure and assign roles.

  4. On the Role tab, use the search box to locate the role you want to assign. Select the role, and then choose Next.

  5. On the Members tab:

    • For the Assign access to value, select User, group, or service principal .
    • For the Members value, choose + Select members to open the Select members flyout panel.
    • Search for the Microsoft Entra group you created earlier and select it from the filtered results. Choose Select to select the group and close the flyout panel.
    • Select Review + assign at the bottom of the Members tab.

    A screenshot showing how to assign a role to the Microsoft Entra group.

  6. On the Review + assign tab, select Review + assign at the bottom of the page.

Set the app environment variables

At runtime, certain credentials from the Azure Identity library, such as DefaultAzureCredential, EnvironmentCredential, and ClientSecretCredential, search for service principal information by convention in the environment variables. There are multiple ways to configure environment variables depending on your tooling and environment. You can create an .env file or use system environment variables to store these credentials locally during development.

Regardless of the approach you choose, set the following environment variables for a service principal:

  • AZURE_CLIENT_ID: Used to identify the registered app in Azure.
  • AZURE_TENANT_ID: The ID of the Microsoft Entra tenant.
  • AZURE_CLIENT_SECRET: The secret credential that was generated for the app.

For C++ applications, you can set these environment variables in several ways. You can load them from a .env file in your code, or you can set them in your system environment. The following examples show how to set the environment variables in different shells:

export AZURE_CLIENT_ID=<your-client-id>
export AZURE_TENANT_ID=<your-tenant-id>
export AZURE_CLIENT_SECRET=<your-client-secret>

Authenticate to Azure services from your app

The Azure Identity library provides various credentials—implementations of TokenCredential adapted to supporting different scenarios and Microsoft Entra authentication flows. Use the ClientSecretCredential class when working with service principals locally and in production. In this scenario, ClientSecretCredential reads the environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_CLIENT_SECRET to get the application service principal information to connect to Azure.

  1. Add the azure-identity-cpp package to your application using vcpkg.

    vcpkg add port azure-identity-cpp
    
  2. Add the following lines in your CMake file:

    find_package(azure-identity-cpp CONFIG REQUIRED)
    target_link_libraries(<your project name> PRIVATE Azure::azure-identity)
    
  3. For any C++ code that creates an Azure SDK client object in your app:

    1. Include the azure/identity.hpp header.
    2. Create an instance of ClientSecretCredential.
    3. Pass the instance of ClientSecretCredential to the Azure SDK client constructor.

    An example is shown in the following code segment:

    #include <azure/identity.hpp>
    #include <azure/storage/blobs.hpp>
    #include <iostream>
    #include <memory>
    
    // The following environment variables must be set before running the sample.
    // * AZURE_TENANT_ID: Tenant ID for the Azure account.
    // * AZURE_CLIENT_ID: The Client ID to authenticate the request.
    // * AZURE_CLIENT_SECRET: The client secret.
    std::string GetTenantId() { return std::getenv("AZURE_TENANT_ID"); }
    std::string GetClientId() { return std::getenv("AZURE_CLIENT_ID"); }
    std::string GetClientSecret() { return std::getenv("AZURE_CLIENT_SECRET"); }
    
    int main() {
        try {
            // Create a credential - this will automatically read the environment variables
            // AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_CLIENT_SECRET
            auto credential = std::make_shared<Azure::Identity::ClientSecretCredential>(GetTenantId(), GetClientId(), GetClientSecret());
    
            // Create a client for the specified storage account
            std::string accountUrl = "https://<replace_with_your_storage_account_name>.blob.core.windows.net/";
            Azure::Storage::Blobs::BlobServiceClient blobServiceClient(accountUrl, credential);
    
            // Get a reference to a container
            std::string containerName = "sample-container";
            auto containerClient = blobServiceClient.GetBlobContainerClient(containerName);
    
            // Get a reference to a blob
            std::string blobName = "sample-blob";
            auto blobClient = containerClient.GetBlobClient(blobName);
    
            // TODO: perform some action with the blob client
            // auto downloadResult = blobClient.DownloadTo("path/to/local/file");
    
            std::cout << "Successfully authenticated and created Azure clients." << std::endl;
    
        } catch (const std::exception& ex) {
            std::cout << "Exception: " << ex.what() << std::endl;
            return 1;
        }
    
        return 0;
    }