11

We are developing an application with a frontend and a backend. The backend should be accessed via Rest API with an OAuth2 token. Authorization provider is Azure AD.

In Azure we created 2 app registrations. One for the API, one for the client app. The API registration defines 3 scopes (Read, Write, Delete). The client app registration has delegated permission for these scopes.

We are requesting tokens with the clientID and clientSecret from the client app registration.

The problem is that we can only request tokens with scope api/.default. E.g. api/read results in invalid scope error. But if we use api/.default, no scope (scp) attribute is included in the token. Isn't that needed to check if the app consuming the API has the right permissions?

I am not sure if we are doing something wrong or if we have a wrong understanding/expectation.

Peter Petrus
  • 699
  • 3
  • 8
  • 15
  • Take a look at this link: https://learn.microsoft.com/en-us/azure/active-directory-b2c/client-credentials-grant-flow It describes on how to create a secured API + client and how you can protect your API with roles (AADB2C uses roles instead of scopes in client-credentials-flow). You need to set roles via the Manifest file instead of via 'Expose an API'. If you would like to know the difference between 'roles' and 'scopes' then this explanation can help you out: https://stackoverflow.com/questions/60942114/oauth-2-0-jwt-guidance-about-when-to-use-scope-vs-roles/60943090#60943090 – JonasVH Jun 30 '22 at 07:44

2 Answers2

18

When using Client Credential flow to get Azure AD JWT token, the scope has to be in the format of

api://<clientid of the API app registered>/.default

As per MSDN, OAUTH Client Credential Flow

scope - Required - The value passed for the scope parameter in this request should be the resource identifier (application ID URI) of the resource you want, affixed with the .default suffix. For the Microsoft Graph example, the value is https://graph.microsoft.com/.default. This value tells the Microsoft identity platform that of all the direct application permissions you have configured for your app, the endpoint should issue a token for the ones associated with the resource you want to use. To learn more about the /.default scope, see the consent documentation.

Anish K
  • 798
  • 4
  • 13
  • okay, But shouldn't I be able to see the allowed scopes in the token so that I can check in the application code if the consuming app is authenticated to read, write or delete things? – Peter Petrus Mar 24 '21 at 10:12
  • The process you mention is termed as 'Authorization' and the scope will be available with the token. You should define your Authorization process in the code by using Class Library provided by Microsoft. Check out this link - https://learn.microsoft.com/en-us/azure/active-directory/develop/microsoft-identity-web Also, if you want to see the scope for your understanding purpose, you can decode it using https://jwt.ms/. Hope it helps. – Anish K Mar 24 '21 at 18:14
  • 1
    As I said, the scope is not encoded in the token when requested with /.default. The 'scp' attribute is completely missing. So I don't know how to check for scopes in the code if no scope is encoded in the token. – Peter Petrus Mar 25 '21 at 11:08
  • @PeterPetrus we you able to figure out on how to get the token scp for a specified scope ? – VR1256 Jun 16 '21 at 17:36
  • The scope is application scope? So it's whatever permissions the application has. As there is only one application, is there a need for individual scopes with each token? – trees_are_great Jun 17 '21 at 08:04
  • Thank you. For me it helps. The result is "const options = { scopes: ['https:// graph .microsoft .com/.default'] }" (remove spaces in url) – Oleksii.B Nov 30 '21 at 13:50
  • ever found a solution @PeterPetrus ? – Carl-Johan Feb 02 '22 at 17:45
  • in azure auth, `scopes` and `roles` are two different types of permission, `client credentials` grant flow uses only `roles`, which is `application permissions`, you should find `roles` claim in the token. Whereas `authorization code` grant flow uses `scopes` which is on behave of a user, so the `delegated permissions`, from there you will find the `scp` claim in the token. – Xiang ZHU Feb 17 '23 at 17:01
14

The claims contained in the token returned by Azure AD depends on the OAuth2 grant type being used. When using a Client Credentials flow it implies that two applications, of which neither involves any user interaction, are being used. Azure documentation uses the terms daemon app and web API app. The daemon app is the application calling API's of the web API app.

The obvious but unfortunately wrong way is to use "Expose an API" e.g. on Azure portal: AD -> application -> Expose an API to create permissions. The problem is that "Expose an API" creates only delegated permissions. But delegated permissions are only relevant when a user is involved. Which is not the case when using a Client Credentials grant.

What one needs to create are application permissions. These permissions can, so it seems, currently only be created directly in the Manifest. The following is needed to create an application permission:

  1. Go to Web API app in AD -> click on Manifest
  2. Change the appRoles field to something like this:
    {
       // ...
       "appRoles": [
         {
            "allowedMemberTypes": ["Application"],          // Must be "Application"
            "description": "Allows Read operation",
            "displayName": "Read",
            "id": "a35fcf6e-58c4-42af-937d-f43e90103b44",   // A unique UUID
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "value": "Read"                                 // The role one wants to create
          }
       ]
       // ...
    }    
    
  3. Save the file

More information can also be found on the official Azure documentation page Protected web API: App registration.

Now one can go to the daemon app in the AD and grant the created application permissions. In Azure portal this is done by these steps:

  1. Go to Daemon app in AD
  2. Go to API permissions
  3. Click on Add a permission
  4. Select My APIs
  5. Select the Web API app
  6. Select Application permissions
  7. Now select the permissions the Daemon app gets granted
  8. Click on Add permission
  9. Grant admin consent by clicking on Grant admin consent for ...

The daemon app can now request a token using client credentials grant. The scope in the request must be '/.default'. (Only for delegated permissions one can ask for non-default scope.) The returned token will then contain the claim roles which is a list of granted permissions. The permissions in the list are the permissions granted to the Damon app. E.g.

"roles": ["Read"]
tiropp
  • 141
  • 1
  • 3
  • 2
    This answer deserves more attention. The distinction of roles vs scopes in user vs app requests is very important and not clearly descibed almost anywhere (except for this excelent answer). Just to add to this in 2022: you can add application permissions using the "app roles" tab of the server app reg, which in turn can be used with "application permission" granting for the client app reg. – Jens Caasen Sep 12 '22 at 11:22
  • 1
    This must be marked as the correct answer. As pert the Microsoft documentation, adding a scope creates only delegated permissions. If you are looking to create application-only scopes, use 'App roles' and define app roles assignable to application type. – Farhan stands with Palestine Nov 24 '22 at 12:51
  • Specifically, this answer explains that the scope request STILL requires "/.default". When creating an app role, a URI is created but it's not explained in the documentation that the URI is used to validate the role but has nothing to do with the scope in the token request. The documentation for this is very weak and missing examples. It seems all API samples only request tokens to Graph - which is useful but not the universe. – Brett Apr 06 '23 at 04:26