I have set up an API in Azure API Management Services, that I want to secure with OAUth2. The backend service is actually an on-premise reset endpoint, with an ExpressRoute connection between azure and our organization.
To enable OAuth, I've followed tutorials online as follows:
Create a "backend" app registration for the backend service, with with default values.
Create a "client" app registration for the client application, with with default values
I create an API and operation, and configure the api with an inbound validate-jwt, and mock-response policy (mock response because my backend service on-prem is not complete yet).
With Postman, I can retrieve a token from the endpoint, but when I use that token to call the api, I just get an invalid or missing token back
Admittedly, I'm not clear on exactly how the two app registrations work together, and what part each plays, but I'm guessing that maybe the issue is in my policy, or in the backend registration, seeing as I can at least get a token?
This is my api policy:
<policies>
<inbound>
<base />
<!-- Look for authorization header bearer token and validate the token against the identity provider -->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud">
<value>api://bcaa2716-xxxx-xxxx-xxxx-09eca63145d3</value>
</claim>
</required-claims>
</validate-jwt>
<mock-response status-code="200" content-type="application/json" />
</inbound>
<!-- Control if and how the requests are forwarded to services -->
<backend>
<base />
</backend>
<!-- Customize the responses -->
<outbound>
<base />
</outbound>
<!-- Handle exceptions and customize error responses -->
<on-error>
<base />
</on-error>
</policies>
Token Request:
POST https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.microsoftonline.com
Content-Length: 95
client_id=1ee63a96-xxxx-xxxx-xxxx-d33a13cafdcc&scope=api://bcaa2716-xxxx-xxxx-xxxx-09eca63145d3&client_secret={secret}&grant_type=client_credentials
Token Response:
HTTP/1.1 200 OK
...
Content-Length: 1369
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiJK...XwkpxaKpw"}
API Request
POST https://xxxxx-apim.azure-api.net/testoauth2/test HTTP/1.1
Ocp-Apim-Subscription-Key: 24b2xxxxxxxxxx3c192a03ca
Authorization: Bearer eyJ0eXAiOiJK...XwkpxaKpw
Content-Length: 0
API Response
HTTP/1.1 401 Unauthorized
Content-Length: 85
Content-Type: application/json
Request-Context: appId=cid-v1:af45326e-7707-4d17-af03-c890faa72200
Date: Fri, 09 Aug 2024 18:24:01 GMT
{ "statusCode": 401, "message": "Unauthorized. Access token is missing or invalid." }
The audience you should configure in your resource server (Azure APIM) is its client ID. You can think of this as a logical identifier to represent one or more APIs.
bcaa2716-xxxx-xxxx-xxxx-09eca63145d3
To get a working solution I would use minimal configuration initially, just to get the token signature validation working. Also ensure that you do some work on server side error logging so that you can diagnose failures.
Once working, you should add checks for all of these. You can compare against values in an access token using any online JWT viewer:
Issuer = https://login.microsoftonline.com/{tenant-id}/v2.0
Audience = bcaa2716-xxxx-xxxx-xxxx-09eca63145d3
Scope = api://bcaa2716-xxxx-xxxx-xxxx-09eca63145d3/.default
See the Microsoft docs for details. Eg there are special elements where you express the <issuers>
and <audience>
, whereas the scope check is expressed under required claims.