azureoauth-2.0azure-api-managementapim

Securing an azure API with oauth: Able to retrieve a token but token is invalid


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:

  1. Create a "backend" app registration for the backend service, with with default values.

    • client id = bcaa2716-xxxx-xxxx-xxxx-09eca63145d3
    • Expose api. Appliation ID URL = "api://bcaa2716-xxxx-xxxx-xxxx-09eca63145d3"
    • Created an app role: "Submit.ADT"
  2. Create a "client" app registration for the client application, with with default values

    • client id = 1ee63a96-xxxx-xxxx-xxxx-d33a13cafdcc
    • Added a client secret
    • Added api permissions for the app role of the backend app registration, and granted admin consent.
  3. 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).

  4. 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

    • grant type = "client_credential"
    • client id = 1ee63a96-xxxx-xxxx-xxxx-d33a13cafdcc
    • client secret = "client" app registration client secret
    • scope = api://bcaa2716-xxxx-xxxx-xxxx-09eca63145d3/.default

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." }

Solution

  • 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.