Why does Azure AD issue access tokens with client credentials grant for any resource?

A G Smith 41 Reputation points
2020-06-29T11:41:06.043+00:00

I am able to get access tokens from Azure AD with client credentials grant irrespective of the scope (resource), is this expected?

  1. Register a new application with AAD in tenant A (single or multi-tenant)
  2. Do not configure any API Permissions for this app
  3. Set up client secret
  4. Request access token from https://login.microsoftonline.com/<tenant_A_id>/oauth2/v2.0/token with:
    client_id: <app id of the new client app>
    client_secret: <secret>
    grant_type: client_credentials
    scope: <any other app uri in any tenant>/.default

Provides me with a valid access token.

For example if I set the scope to https://outlook.office.com/.default for the outlook 365 app I can get an access token.

I would have instead expected to get a message of this sort:

{"error":"invalid_resource","error_description":"AADSTS500011: The resource principal named https://outlook.office.com was not found in the tenant named <tenant_A_id>. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\nTrace ID: ... "error_uri":"https://login.microsoftonline.com/error?code=500011"}

I could not find any documentation detailing this behaviour so was wondering if this was working as expected?

I did find this announcement: https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-breaking-changes#july-2019

Previously, applications were allowed to get tokens to call any other app, regardless of presence in the tenant or roles consented to for that application. This behavior has been updated so that for resources (sometimes called web APIs) set to be single-tenant (the default), the client application must exist within the resource tenant. Note that existing consent between the client and the API is still not required

I am now wondering if I will need to create custom code in my APIs to validate the tokens, e.g. based on the client id used to request them, I was expecting this to be "handled" by AAD, in that if there was no permission set up on the client application registration for a given API - no token would be generated by AAD.

Please advise?

Regards

A G Smith

------

UPDATE 15 Feb 2021

Since asking this I have observed an even more generalised case: a client id / secret for a multi-tenant app reg in Tenant B, can be used to request tokens from any other tenant, resulting in an access token carrying the tid claim of the requested tenant:

  1. Register a new application with AAD in tenant B as multi-tenant
  2. Set up client secret for this app reg
  3. Do not configure any API Permissions for this app
  4. Request access token from "https://login.microsoftonline.com/<any valid tenant id>/oauth2/v2.0/token" with:
    client_id: <app id of the new client app in tenant B>
    client_secret: <secret>
    grant_type: client_credentials
    scope: <any other app uri in any tenant>/.default

Provides me with a valid access token, with the following claims:

{
"aud": "<any other app uri in any tenant>",
"iss": "https://sts.windows.net/<any valid tenant id>/",

"appid": "<app id of the new client app in tenant B>",

"tid": "<any valid tenant id>",
}

The key difference here is that the client id/secret I am providing resides against an app reg in an entirely different tenant to that specified in the URL of the token request. The received token "echos" back the tenant id I have put in the URL (which could be any), making any checks the application code makes against the tid claim irrelevant as that can be controlled by the agent making the token request.

I accept that all of this can be mitigated by ensuring API permissions are set up and role/scp claims are checked in the application code (as per the answer below), but this is again not obvious from the documentation, an implementer might be forgiven for thinking the tenant id (tid) claim would be a "safe" way to check that a client is coming from the right AAD tenant.

aud String, an App ID URI or GUID Identifies the intended recipient of the token - its audience. Your API should validate this value and reject the token if the value doesn't match. In v2.0 tokens, this is always the client ID of the API, while in v1.0 tokens it can be the client ID or the resource URI used in the request, depending on how the client requested the token.

Your documentation suggests the aud claim should be checked to validate the token but this is not sufficient as it can be controlled by the agent making the token request (just by modifying the scope requested)

appid String, a GUID Only present in v1.0 tokens. The application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Azure AD.

To ensure this token has been "issued" by the right application reg the appid claim must be checked in the application code, but according to your docs this is only present in v1.0 tokens, however it seems to always be provided even when calling the v2 end-point.

Regards
A G Smith

Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
20,446 questions
0 comments No comments
{count} votes

Accepted answer
  1. 2020-06-30T00:20:21.897+00:00

    You're not getting AADSTS500011 since the service principal for https://outlook.office.com/.default is a multi-tenant application. Try targeting a single-tenant resource (application) and you will get the expected error. For more information of how to validate tokens and permissions in your api take a look to Protected web API: Code configuration and subsequent chapters.

    3 people found this answer helpful.

6 additional answers

Sort by: Most helpful
  1. Vasil Michev 99,936 Reputation points MVP
    2020-06-29T17:40:11.977+00:00

    AAD handles authentication, not authorization. You would get a token, but it will be "empty" when it comes to scopes/roles (as in the actual claim), so when you present it to the actual resource, it will be declined. Now, it the resource doesn't exist in the tenant (for example the tenant has not provisioned Exchange or Teams), I would definitely expect an error to be generated. But that are just my thoughts as a non-programmer :)

    3 people found this answer helpful.
    0 comments No comments

  2. Hirsch Singhal [MSFT] 16 Reputation points
    2020-07-10T16:08:47.803+00:00

    Just wanted to confirm that @michev is correct here. App-only tokens are currently issued between SPs int he same tenant regardless of roles on the token. That is expected as authentication has been satisfied, and authorization is handled by the resource. You can enable "require role assignment" on the API you are concerned about, but this is heavy weight and impacts both app-only tokens and delegated (user) tokens.

    I'm working on some documentation now for this to follow-up from the breaking change you wrote. If you have any suggestions on where you'd expect this to be documented, that would be great - we want this to be discovered where developers look for it.

    //Hirsch Singhal, OAuth PM for Azure AD

    2 people found this answer helpful.

  3. JamesTran-MSFT 36,531 Reputation points Microsoft Employee
    2020-07-14T19:02:48.217+00:00

    I completely agree with you that all this information is in the "docs somewhere but not immediately clear". It also looks like we have Hirsch, one of our OAuth PMs looking into this issue as well. Regarding your documentation recommendations, I was able to look into this and will post my findings below.

    Behavior of the STS around dispensing access tokens for multi-tenant apps - I wasn't able to find too much on this.

    Difference between delegated and application permissions

    scp and roles claims

    Significance of the ".default" scope with the client credentials grant specifically. The /.default scope. This doc defines a default scope and goes into consent. Understand user and admin consent, for single and multi-tenant applications. Description of the scope parameter when using "graph.microsoft.com/.default"

    Additional link: Authentication vs authorization

    Hopefully this helps!

    1 person found this answer helpful.
    0 comments No comments

  4. testuser7 271 Reputation points
    2021-02-26T22:14:52.547+00:00

    This was a great discussion and helped me too !!!
    @Vasil Michev @Hirsch Singhal [MSFT] So just to solidify, if I understood correctly...

    as long as the service-principal of any app (SP1) is present in my tenant , my app can send a client-credential flow and get a token where AUDIENCE is SP1
    I need not have to configure any "application permissions" in my app which are exposed by SP1

    What about the delegated flow ?
    Do I have to prior-configure any delegated permissions in my app which are exposed by SP1 ?
    OR
    I can just send a request to /authorize endpoint with scope= < app-uri of any app whose SP is in my tenant>/.default without configure any required-resource-access information.

    Thanks.

    0 comments No comments