3
votes

Given

  • We have a .NET Web API service.
  • It secures access to controllers and actions using the AuthorizeAttribute with Roles. For example:
[Authorize(Roles = "Reader,Requester,Editor,Approver,Administrator")]
  • There is an App Registration within Azure Active Directory for this application.
  • It uses OAuth for user impersonation, and users are authenticating correctly. There are many tutorials available on getting to this point.

Intent

  • We wish to have an unattended scheduled process (a script) invoke REST calls on the Web API.
  • The identity of the unattended process is not available in Azure Active Directory.

    In my specific scenario, this is because we don't sync our numerous on-premises AD service accounts to AAD, and this script will be running from an on-premises service principal identity.

  • We wish to use the AuthorizeAttribute to control our script's access to controllers/actions within the Web API.

How can I make this work?

1

1 Answers

3
votes

Method

One of the constraints is that the unattended process' user identity will not exist in Azure Active Directory; so we will use OAuth 2.0 client credentials grant flow for this scenario.

Step 1: Gather Information

First, identify the following pieces of information:

  1. Client ID
  2. Tenant ID
  3. Authority Url
  4. Resource Url
  5. Client Secret

You probably already have most of these if you've got OAuth working for users and roles. If not, here is what they are and where to find them.

1a: Finding the Client ID

The client ID is a GUID, and is the application's ID in Azure Active Directory. It is not the Object ID (which is different).

Azure Portal: Azure Active Directory > App registrations > [your app] > Overview blade > Application (client) ID field.

PowerShell, from a logged-in context:

$(Get-AzADApplication -DisplayName "[your app name]").ApplicationId.Guid

1b: Finding the Tenant ID

The Azure tenant ID is a GUID.

Azure Portal: Azure Active Directory > App registrations > [your app] > Overview blade > Directory (tenant) ID field.

PowerShell, from a logged-in context:

$(Get-AzContext).Tenant.Id

Azure CLI, from a logged-in context:

az account show --query 'tenantId' -o tsv

1c: Finding the Authority Url

The Authority Url is the URL of the OAuth authorization server. It looks something like this: https://login.microsoftonline.com/[your-tenant-id]/oauth2/v2.0/token

Azure Portal: Azure Active Directory > App registrations > [your app] > Endpoints button > OAuth 2.0 token endpoint (v2) field.

1d: Finding the Resource Url

The Resource Url is the URL of the Web API service. It probably looks like this: https://[yourdomain].onmicrosoft.com/[guid]

Azure Portal: Azure Active Directory > App registrations > [your app] > Expose an API blade > Application ID URI field.

It is also located in the application manifest in the identifierUris field. Azure Portal: Azure Active Directory > App registrations > [your app] > Manifest.

Manifest attribute example:

"identifierUris": [
    "https://[yourdomain].onmicrosoft.com/[guid]"
]

PowerShell, from a logged-in context:

$(Get-AzADApplication -ApplicationId [ClientId]).IdentifierUris

Azure CLI, from a logged-in context:

az ad app show --id [ClientId] --query 'identifierUris' -o tsv

1e: Creating the Client Secret

The client secret can be a client secret (key/password) or a certificate. Here is how to create a client secret.

Azure Portal:

  1. Go to Azure Active Directory > App registrations > [your app] > Certificates & secrets blade > Client secret section.
  2. Press the New client secret button and complete this process.
  3. Copy the key value; this is the client secret. Don't lose it.

PowerShell: use the New-AzADAppCredential cmdlet.

Step 2: Configure the Azure Active Directory Application

Since you are using AuthorizeAttribute Roles to control access, you must add the application to at least one of those roles. Roles are defined in the application manifest under the appRoles attribute.

2a: Make roles that Applications can belong to

Each role has an allowedMemberTypes attribute. If you've already configured this application for users, then you already have something like this:

"allowedMemberTypes": [
    "User"
],

To allow your application to be added to a role, modify it like this:

"allowedMemberTypes": [
    "User",
    "Application"
],

Alternately, you could have a role that allows only applications.

"allowedMemberTypes": [
    "Application"
],

2b: Add Application to its Roles

Now that there are roles that the application can belong to, you must add the application to those roles.

Azure Portal: Azure Active Directory > App registrations > [your app] > API permissions blade.

  1. Press the Add a permission button.
  2. Select the APIs my organization uses tab.
  3. Find and select your application.
  4. Press the Application permissions box.
  5. Select the permissions (roles) for this application.
  6. Finally, press the Add permissions button.

2c: Grant admin consent

If these roles require admin consent, then you will need to grant admin consent now.

Azure Portal: Azure Active Directory > App registrations > [your app] > API permissions blade > Grant consent section. Press the Grant admin consent for [your org] button, and confirm Yes.

If you don't have permissions to do this, find someone in the Application Administrator role or someone else with similar permissions to do this for you.

Step 3: Verify

At this point you should be able to use the OAuth 2.0 client credentials flow to obtain an access token, present it as a Bearer token with your request to your Web API service, and succeed.

If you want to verify using Postman or similar tool, use this guide to create the requests.

When you get your hands on a token, you can examine the contents using this tool: https://jwt.io/ Verify that there is a roles attribute in the token, and that it is populated with the roles you assigned in the previous step.

For example:

{
    …
  "azpacr": "1",
  "roles": [
    "Approver",
    "Reader"
  ],
  "ver": "2.0"
    …
}

Here is a PowerShell script showing how to do this using the ADAL.PS module:

Import-Module ADAL.PS
$tenantId = "[tenant id]"
$authority = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/authorize"
$resourceUrl = "[resource url]"
$clientId = "[client id]"
$secret = ConvertTo-SecureString -String [client secret] -AsPlainText -Force
$response = Get-ADALToken -Authority $authority -Resource $resourceUrl -ClientId $clientId -ClientSecret $secret
$token = $response.AccessToken
$response
$restResponse = Invoke-RestMethod -Method Get -Uri "[your web api uri]" -ContentType "application/json; charset=utf-8" -Headers @{ Authorization = "Bearer $token" } -Verbose -Debug
$restResponse

Step 4: Secure your Secret

Now you've got this secret in your unattended script or job. That's probably not a great idea, so secure that somehow. How you do it is outside the scope of this answer.