Pablo Cibraro

My notes about software development, security and other stuff.

OAuth Client Credentials in Azure AD explained for Web API developers

Client credentials is one of the OAuth 2.0 flows primarily designed for addressing server-to-server scenarios (e.g., server code in a web app invoking a web API). It also fits within the category of confidential flow, as the authentication credentials must be stored securely in a private location. Anyone that gains access to those credentials can negotiate an access token.

The Resource Owner Password Flow (ROP) is another option for supporting a similar scenario. However, there is an important distinction between those two flows. ROP requires an additional username and password to impersonate the call on behalf of a user. It is often abused and used as a replacement for the authorization code flow when embedding a web browser in the client application is not feasible. It is no longer recommended as the client application code can access the user's password, and MFA can be skipped.

If you do not need to impersonate any user, client credentials is the way to go.

Client applications and Web APIs are represented in Azure AD as App Registrations. When you attach a set of credentials for authentication, like a pair of client id and secret or a client certificate to an App Registration, it becomes a Service Principal that can be used for the Client Credentials flow.

For client certificates, the App Registration is only associated with the public key of the certificate. The client application issues a JWT signed with the private key, which also contains the Application ID for the App Registration, and passes that to the Authorization Server (Azure AD) for authentication. Azure AD extracts the Application ID for App Registration from the JWT, and validates the signature on the JWT with the public cert for that App Registration. If the signature verification passes, the client application is successfully authenticated.

For client id and secret, authentication is much simpler. Azure AD only compares the passed client id and secret against the ones attached to the App Registration.

While client applications and Web APIs are both App Registrations, the properties you configure for them are entirely different. We will go through the process of registering each to discuss the differences.

Registering a Web API

A Web API requires a URI scope. That is a unique ID for identifying the API. It could be the URL where the API is hosted or any other URN. Keep in mind two things when assigning this scope to an API.

  • It should be unique across all the APIs in your Azure AD tenant and something you can easily recognize and associate with the API. The latter is important as the client application will use that ID as scope for requesting an Access Token for the Web API to Azure AD.

  • You can use something like a GUID, but it will be hard to correlate/associate to a Web API by simply looking at the code or script used for getting a new access token.

You can associate Roles to an App Registration representing a Web API. Those roles are not OAuth scopes because they are not requested or consented to by the users. They are assigned by the owner of the Web API in Azure AD to one or more users or other app registrations. Suppose Azure AD determines the caller application or the user impersonating the call belongs to one of the available roles in the Web API. In that case, it will inject those in the access token (JWT) under the roles attribute. You can define two types of Roles, Application Roles and User Roles, also known as Delegated Roles.
Application Roles only work for the Client Credentials flow where Authorization is performed in the context of the caller application. The Delegated Roles are used for the rest of OAuth flows and OpenID Connect.

When you create a new role for your web API, you can set the type, which can be Application, User (Delegated Role), or both. Select Application if you are only planning to use it for client credentials.

Many of the roles that Microsoft defined for the Azure APIs use the following naming convention, <resource>.<action>, which permits you to do <action> on <resource>. For instance, Users.ReadWrite on the Graph API allows any client application to read or update users with that API.

Roles are not global. As we said before, they are defined in the context of a Web API. Users.Write does not mean anything for Azure if it is not passed in the context of the Graph API. Azure AD uses the URI scope to identify the API, and then looks for the requested role in that API only.

Go to Azure Active Directory in your Azure Subscription to create a new App Registration for a Web API.

Go to the App Registrations option on the left menu for Azure Active Directory and click on "New Registration"

alt

Complete the name of your Web API and click on "Register".

alt

Go to the "Expose an API" option on the left menu for this App Registration. Click on the "Set" link for the Application ID URI. That will set the URI Scope for the API. Configure the following value as URI "api://myapis/mywebapi".

alt

Go to the App roles option on the left menu for this App Registration.

Click on "Create App Role".

Fill out the following details:

Display Name: Admin Role

Allowed member types: Applications

Value: Admin

Description: Allows executing any operation on the web api

Click on "Create App Role".

Fill out the following details:

Display Name: Read-Only Role

Allowed member types: Applications

Value: ReadOnly

Description: Allows read-only access on the web api

After following all these steps, you should have a new Web API that supports two roles, Admin and ReadOnly.

Registering a client application

A client application is another App Registration, but it will be used to negotiate an access token for consuming a Web API. For that reason, there are important things to define in a client application.

  • The authentication credentials, which could be a pair of client id and secret, or a client certificate.

  • The roles/permissions required on a Web API. This part if optional. If you don't define any authorization role for a Web API, any client application registered in the same Azure AD tenant will be available to get an access token to consume it.

Go to Azure Active Directory in your Azure Subscription to create a new App Registration for the client application.

Go to the App Registrations option on the left menu for Azure Active Directory and click on "New Registration".

Complete the name of your client application and click on "Register".

Go to API permissions on the left menu for this App Registration.

Click on the "Add a permission" link.

alt

Go to the tab "APIs my organization uses" and look for the Web API previously registered.

The roles for the Web API will be listed, those are "Admin" and "ReadOnly". Select the role you want to grant to this client application.

alt

Application Roles assigned to an App Registration are not automatically granted. The administrator for the Azure AD tenant must approve them first. That can be done by clicking on the option "Grant Admin consent". This step is very important as Azure AD won't inject any role into the access token if the Admin consent has not been granted.

Go to "Certificates & Secrets" option on the left menu for this App Registration.

Click on "New client secret". Enter a description to identify the secret and an expiration.

alt

Take note of the "Value" for the new secret, as you won't be able to retrieve it next time. "Value" will be the client's secret for authentication. The client id is the App ID associated with the App Registration. Make sure to distinguish with Secret ID, which is not really used at all.

Get an access token from Azure AD

Getting a new fresh access token or JWT from Azure AD with client credentials requires an HTTP POST with the following details,

URL: https://login.microsoftonline.com/{tenant}//oauth2/v2.0/token
Content-Type:  application/x-www-form-urlencoded
Body: 

client_id={client id}
&scope={web api uri scope}/.default
&client_secret={client secret}
&grant_type=client_credentials

Tenant: It's the tenant ID where the App Registration for the Web API and Client were created.

Client Id: It's the App Id assigned to the App Registration created for the client.

Scope: It's the Web API URI scope that was assigned when the App Registration for the Web API was created. Azure AD uses an awkward convention not documented anywhere for the scope. When you use client credentials, "/.default" must be appended to the URI scope. No other value is allowed. This scope is used by Azure AD to determine which API the client application wants to consume and generate an access token for it. This will drive the generation of the JWT and how some of its attributes are set. For instance, the audience or AUD attribute will be the set with the App ID for the Web API, and roles attribute with all the roles assigned to the client in that given API. For our example, the complete value would be api://myapis/MyWebAPI/.default Client Secret: It's the secret assigned to the App Registration for the client.

Anatomy of a JWT (Access Token)

A JWT returned by Azure AD looks as follow.

{
  "aud": "api://myapis/MyWebAPI",
  "iss": "https://sts.windows.net/f6aba1d9-da41-4ea3-8f30-4970725586b9/",
  "iat": 1672924530,
  "nbf": 1672924530,
  "exp": 1672928430,
  "aio": "E2ZgYHD9N/XE5ot5He2iV1Z1VfAyAAA=",
  "appid": "55b2a7ec-73f3-45c2-af08-21ecc33dc40e",
  "appidacr": "1",
  "idp": "https://sts.windows.net/f6aba1d9-da41-4ea3-8f30-4970725586b9/",
  "oid": "1f3086f6-9164-45f2-b479-a93f64d1006a",
  "rh": "0.AX0A2aGr9kHao06PMElwclWGqGL1t9EZOxJMkZf3CB4gW3ucAAA.",
  "roles": [
    "Admin"
  ],
  "sub": "1f3086f6-9164-45f2-b479-a93f64d1007a",
  "tid": "f6aba1d9-da41-4ea3-8f30-4970725586a8",
  "uti": "G-1ZiIhWbkKJdvkZfawxAA",
  "ver": "1.0"
}

aud: The audience for the access token. This is used by the Web API to validate that the token was actually issued for it.

iss: The issuer ID. This is an unique identifier for the Azure AD tenant.

sub: The client ID

roles: The roles assigned to the client on the Web API.