This article is contributed. See the original author and article here.
Use MITREid Connect for OAuth2 Authorization in API Management
By (alphabetically): Akinlolu Akindele, Dan Balma, Maarten Van De Bospoort, Minhui Burket, Nick Drouin, Heba Elayoty, Andrei Ermilov, Tom Fleming, David Giard, Michael Green, Alfredo Chavez Hernandez, Hao Luo, Siva Mullapudi, Kamil Somaj, Nsikan Udoyen, William Zhang
Introduction
Using an API gateway in front of REST APIs is a common design pattern which allows us to offload the cross-cutting capabilities such as OAuth2 authorization to the gateway instead of letting security code get scattered in application code. Azure API Management (APIM) is such an API gateway service. For the common case that the OAuth2 server in APIM is Azure AD (AAD), it has been well documented. In this document we will cover the case that the OAuth2 server is MITREid Connect instead of AAD.
MITREid Connect is an open source Identity Provider, popular in Java community. MITREid Connect is compliant to OpenID Connect and OAuth 2.0 protocol. This document is based on MITREid Connect v 1.3.3.
Prerequisites
- An Azure subscription
- A MITREid Connect instance. This document focuses on how to enable OAuth2 over APIM and MITREid Connect, instead of on how to set up MITREid Connect.
- A REST API app for test. The REST API could be deployed to AKS, App Service, or other. Or use this sample REST API with Swagger http://conferenceapi.azurewebsites.net/?format=json.
- Familiar with Azure API Management
- Basic understanding of OAuth2. OAuth2 involves a few specs (OAuth2, OIDC, PKCE and JOSE). A list of good references on OAuth2 and related specs can be found here.
NOTE: This article does not cover the security between APIM and the REST API which is a separate topic. If a REST API is deployed to AKS, there are a few options which have been documented in Use Azure API Management with microservices deployed in Azure Kubernetes Service.
If APIM Premium is used, we can leverage VNET so that the security between APIM and AKS is simpler. Otherwise, we need to secure the REST calls from APIM to AKS thru technique such as mutual TLS (mTLS) (note that it is mutual TLS, not just TLS). This could be the topic of a future document.
Preparations in MITREid Connect
In order to prepare for OAuth2 setup in APIM, we need to perform the following steps in MITREid Connect:
- Register a server application registration;
- Define its scopes;
- Register a client application;
- Specify its permissions: either Delegated Permissions or Application Permissions;
- Create a client secret.
The concepts and the steps for the above OAuth2 steps between MITREid Connect and Azure AD are not fundamentally different. You may get detailed steps from Protect an API by using OAuth 2.0 with Azure Active Directory and API Management
The following parameters from MITREid Connect must be prepared and available for our setup in APIM.
Parameter | Typical Format |
---|---|
Client ID | A string. Unlike Azure AD, a client ID in MITREid Connect does not have to be a GUID. But it needs to be unique within its tenant. |
Client secret | Hexadecimal representation of a cryptographically secure pseudorandom number |
OpenID config endpoint | https://[host]/.well-known/openid-configuration |
Authorization endpoint | https://[host]/authorize/ |
Token endpoint | https://[host]/token/ |
Audience | A string |
Issuer | https://[host]/ |
Default Scopes | Optional, and it could have multiple values |
Create an OAuth2 Server on MITREid Connect
- In Azure portal left menu column, click “OAuth 2.0”
- Click “Add” button on the right pane
- You need the following parameters to create the OAuth 2 server on MITREid Connect:
- Client ID
- Client secret
- Authorization endpoint
- Token endpoint
For Authorization grant types, you may choose to checkmark the following:
- Authorization code
- Client credentials
For Default scope, you can either leave it blank or enter any valid scope as defined in MITREid Connect client registration.
You can keep Resource owner username and Resource owner password as blank.
For additional details on creating OAuth2 server in APIM, please see this document.
Set up a REST API App in APIM
- Create an API Management service by following this document.
- Import a REST API by following this document.
- Make sure to disable subscription-key authorization which is enabled by default.
- Click on “APIs” menu in the left menu column
- Select the API you are working with in the mid-column
- In the right pane, select Settings tab at the top
- Uncheck Subscription required under Subscription section. This ensures that subscription-key authorization is not used since we intend to use OAuth2 authorization.
Customize API Inbound Policy
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="[openid-config endpoint]" />
<required-claims>
<claim name="aud">
<value>[audience]</value>
</claim>
<claim name="iss">
<value>[issuer]<value>
</claim>
<claim name="scope">
<value>[scope 1]</value>
<value>[scope 2]</value>
</claim>
</required-claims>
</validate-jwt>
The openid-config endpoint is the OpenID Config URL from MITREid Connect. It is critical to make sure you have the correct URL since this is the URL thru which APIM acquires JWKS (JSON Web Key Set) for JWT validation.
The values for audience and *issuer” are from MITREid Connect and can be found from its token sample.
For *scope” claim, you can have 0, 1 or multiple scope values. Notice that if you have mutlipel scope values, it means AND (all required) instead of OR (one of scope values is enough).
Troubleshooting Guide
In OAuth2 authorization, it is typical to have multiple parts involved in the authorization flow:
Component | Purpose |
---|---|
Client application | The client application can be either an app for end users or a service/server process depending on the secured REST API. In case of a client app for end users, it can be either a private client which can hide client_secret or a public client which cannot. Depending on use case, different OAuth2 authorization flows can be used. If the client is a service, a Client Credentials Flow is used. For a public end user client, Authorization Code Flow with PKCE is used such as in the OAuth2 Test Tool. |
Identity Provider | Users get authenticated and are issued authorization code which can be used to acquire access_token. |
Token issuer | The MITREid Connect component which issues access_tokens and refresh_tokens |
Token introspector | The APIM component which inspects and validates JWT tokens based on policy settings, such as claims, issuer and audience |
OpenID config endpoint | An endpoint provided by MITREid Connect from which APIM acquires public keys for token introspection. This is based on OpenID Discovery spec. APIM is never configured to hold a static public key from MITREid Connect. |
Registered client app | A client app pointer in MITREid Connect which defines the scopes granted by either admin or users, as well as its client ID and client_secret |
Registered server app | A server app pointer in MITREid Connect which defines an abstraction of REST API and defines the scopes |
Unauthorized (401) Issue
The most common error in OAuth2 authorization is Unauthorized (401). This could be caused by any of the following:
- Incorrect openid-config endpoint in API inbound policy
- Incorrect *iss” value in API inbound policy
- Incorrect “aud” value in API inbound policy
- Incorrect “scope” values in API inbound policy. If you can capture a sample JWT token, you can parse it via a tool like http://aka.ms/jwt to see its claims values. These values are determined by registered server application in MITREid Connect and the API permission configurations for the registered client application.
- Incorrect Security settings in the API. Check the *Settings” tab of the API and make sure that *User authorization” is OAuth 2.0 and the right OAuth 2.0 server is selected in the OAuth 2.0 server dropdown.
Subscription-key Issue
By default, when an API is installed into APIM service, its subscription-key authorization is enabled. Make sure it is disabled since we use OAuth2 instead of subscription-key. You can find its settings under Settings tab in the API.
CORS Issue
If your test client is a web or SPA client and you use javascript to make API calls with OAuth2 authorization, you will likely face CORS issue since the web app or SPA is from different domain as APIM. By default, an API in APIM does not support CORS preflight and you need to enable it in inbound policy.
Inbound section:
<cors allow-credentials="true">
<allowed-origins>
<origin>http://localhost:3000</origin>
</allowed-origins>
<allowed-methods preflight-result-max-age="300">
<method>GET</method>
<method>POST</method>
<method>PATCH</method>
<method>DELETE</method>
<method>PUT</method>
</allowed-methods>
</cors>
Outbound section:
<outbound>
<base />
<set-header name="Access-Control-Allow-Origin" exists-action="override">
<value>@(context.Request.Headers.GetValueOrDefault("Origin",""))</value>
</set-header>
<set-header name="Access-Control-Allow-Credentials" exists-action="override">
<value>true</value>
</set-header>
</outbound>
Custom header issue
Another potential issue is: for a production REST API, it has custom headers such as transaction or correlation ID for logging and troubleshooting. Such correlation ID may be scoped to the enterprise or the service itself.
However, there is a restriction to access response headers when you are using javascript Fetch API over CORS. Due to this restriction, by default you can access only following standard headers:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
More info on this restriction can be found here.
In order to allow such client to get access to custom headers (such as correlation ID), we need to add the following section in our inbound/cors section:
<allowed-headers>
<header>content-type</header>
<header>accept</header>
<header>authorization</header>
<header>x-correlation-id</header>
<header>x-my-request-id</header>
</allowed-headers>
<expose-headers>
<header>x-correlation-id</header>
<header>x-my-request-id</header>
</expose-headers>
Terraform for Deployment
Needless to say, it is desirable to have the creation and configuration automated. We could use Terraform for this purpose. Detailed document on Terraform Azuure API Management Resources can be found here.
The Terraform (.tf file) should cover the following tasks:
- Create a resource group
- Create an API Management service
- Create a product
- Add a REST API into the product
- Disable subscriptin key authorization in the REST API
- Import a prepared inbound policy (XML) into the REST API
- Add OAuth2 Server based on MITREid Connect
- Configure REST API settings
In addition to the variables defined in variables.tf file, the Terraform also requires a XML policy file as input. In the policy file, the following parameters (XML node/attribute values) are required.
XML node | Node attribute | Definition |
---|---|---|
openid-config | url | The OpenID Config endpoint URL for public keys used in token introspection |
claim | name (aud) | Audience defined in the registered server app in MITREid Connect |
claim | name (iss) | The issuer of JWT tokens |
claim | name (scope) | The list of required scopes. All of the listed scopes listed must be present in a token before it can be validated. |
allowed-origins | List of allowed origins for CORS preflights |
The APIM inbound policy with these parameters is critical in ensuring the following to work:
- Token introspection
- CORS policy
- Custom header accessibility
Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.
Recent Comments