0

We have a Python code that tries to query an API deployed on Microsoft Azure. The Code first requests an access token from the API using azure-identity library and then sends the token in the Authorization header of the request like the following:

import requests
from azure.identity import ClientSecretCredential

TENANT_ID = 'my-tenant-id'
CLIENT_ID = 'my-client-id'
CLIENT_SECRET = "my_client-secret"
SCOPES = ['api://my-client-id/.default']

identity_client = ClientSecretCredential(tenant_id=TENANT_ID,
                                         client_id=CLIENT_ID,
                                         client_secret=CLIENT_SECRET,
                                        authority='https://login.microsoftonline.com')

access_token = identity_client.get_token(SCOPES[0])
#Request the API endpoint


json = {
    "function_name": "function_name",
    "param1": "param1_value",
    "param2": "param2_value",
}

headers = {
    "Authorization": f"Bearer {access_token.token}",
    "Content-Type": "application/json"
}
response = requests.get('https://myapi.whatever/myendpoint',
                        json=json, headers=headers)

if response.status_code == 200:
    print(response.json()["result"])
else:
    print(response)

However, also we get an access token (with valid signature on jwt.io); we get the following error/response when we query the endpoint:

{'_content': b'missing_claim',
 '_content_consumed': True,
 '_next': None,
 'status_code': 401,
 'headers': {'Date': 'Fri, 12 May 2023 15:25:27 GMT', 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Request-Context': 'appId=cid-v1:752b04bc-08aa-4002-a618-d3e7be07a371', 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'sameorigin', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer'},
 'raw': <urllib3.response.HTTPResponse at 0x2967109e3a0>,
 'url': 'https://myapi.whatever/myendpoint',
 'encoding': 'ISO-8859-1',
 'history': [],
 'reason': 'Unauthorized',
 'cookies': <RequestsCookieJar[]>,
 'elapsed': datetime.timedelta(microseconds=306335),
 'request': <PreparedRequest [GET]>,
 'connection': <requests.adapters.HTTPAdapter at 0x296710856a0>}

I am not sure what is causing this also we configured the permissions for the API correctly... could anyone have any idea of what is causing this error and how to fix it? Thanks.

Also note that we tried using other libraries like msal for example:

app = msal.ConfidentialClientApplication(
    client_id=CLIENT_ID,
    client_credential=[CLIENT_SECRET],
    authority='https://login.microsoftonline.com/my-tenant-id',
    token_cache=cache,
)

result = None

result = app.acquire_token_silent(scopes=SCOPES, account=None)

if not result:
    print('Here')
    result = app.acquire_token_for_client(scopes=SCOPES)

but still the same error...

Aniss Chohra
  • 391
  • 4
  • 18
  • 401 error means the user acquiring the token (SP in your case) does not have permission to access the resource. Have you given proper permissions to your SP to access the API? – Gaurav Mantri May 12 '23 at 18:00

1 Answers1

0

I agree with @Gaurav Mantri, the error 401 Unauthorized usually occurs if your service principal does not have permissions to call the API.

To confirm that, decode the access token and check whether it has scp claim with API permission or not.

If you added scope in Expose an API tab, it creates delegated permissions like below:

enter image description here

I generated access token using client credentials flow via Postman like this:

POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token

grant_type:client_credentials
client_id: <clientappID>
client_secret: <secret>
scope: api://<webapi_appID>/.default

Response:

enter image description here

When I used above access token to call API, I too got 401 Unauthorized error like below:

GET https://myapiendpoint/weatherforecast

Response:

enter image description here

Note that, client credentials flow won't work with delegated permissions. So, scp claim would be missing in token generated with client credentials flow.

To resolve the error, you have to use delegated flow like authorization code flow, interactive flow or username password flow etc...

In my case, I generated access token using authorization code flow via Postman like this:

POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token

grant_type:authorization_code
client_id: <clientappID>
client_secret: <secret>
scope: api://<webapi_appID>/.default
redirect_uri: https://jwt.ms
code: code

Response:

enter image description here

When I decoded this token in jwt.ms, it has scp claim with API permission like below:

enter image description here

I can make API call successfully with above access token like this:

GET https://myapiendpoint/weatherforecast

Response:

enter image description here

You can check this SO thread to generate access token using auth code flow via Python.

Sridevi
  • 10,599
  • 1
  • 4
  • 17
  • 1
    I agree with Sridevi's answer but I'd like to add that the accessTokenAcceptedVersion property must be set to 2 in the app registration's manifest. – Luis Gouveia May 26 '23 at 09:47