Sunday, April 10, 2022

Secure APIs on Azure behind API Management

anagement has deep integrations with Azure AD which in turn has support for with the OAuth 2.0 & OpenID Connect through Microsoft Identity framework. These two powerful, feature-rich resources can be leveraged to secure backend APIs without adding any code or logic within the APIs itself; thus making the Security of APIs clean, flexible and reusable

 

What this Document will do?

  • A detailed overview of configurations in Azure AD for OAuth 2.0 and OpenID Connect

  • Deep look at the various protection mechanisms available in Microsoft Identity platform

  • How to integrate API Management with OAuth 2.0 and/or OpenID Connect

  • Leverage built-in Policies in Azure API Management to secure the backend APIs

 

Let us get into some Actions

Configure Azure AD

Register an Application to represent the Back end APIs

  • Follow these Steps to register

  • Let us call this ServerApp to better correlate

  • Go to Manifest section of the app and update

    • acceptMappedClaims to true - This is needed to make sure that the Associated Groups are returned as part of the OAuth 2.0 or OpenID token

    • groupMembershipClaims option to SecurityGroup

aad-serverapp.png

 

Configure API Permission section

aad-api-perm.png

 

Add/Modify Token Configuration section

  • Optional and Group claims can be added

aad-token.png

 

Go to Expose API section and Add Scope

aad-expose-api.png

 

Register an Application to represent the Client to Access backend APIs

  • Follow these Steps to register

  • Let us call this ClientApp to better correlate

aad-secrets.png

 

aad-client-api-perm.png

 

Go to Authentication section and modify as below

  • Select Multi-tenant or Single-tenant option as per requirementaad-auth.pngaad-auth2.png

 

Configure API Management

  • Configure as shown in the below screen shots; these are self-explanatory

apim-oauth.pngapim-oauth2.pngapim-oauth3.pngapim-oauth4.png

 

API Management Policy

 
Check the validity of the Bearer Token
<policies>
   <inbound>
       <base />
     <!--Check the validity of the Bearer Token-->
       <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="UnAuthorized">
           <openid-config url="https://login.microsoftonline.com/3851f269-b22b-4de6-97d6-aa9fe60fe301/.well-known/openid-configuration" />            
       </validate-jwt>        
   </inbound>
   <backend>
       <base />
   </backend>
   <outbound>
       <base />
   </outbound>
   <on-error>
       <base />
   </on-error>
</policies>

 

Check for Claims in the Bearer Token
  • Let us check the Audience value of token

  • Audience in JWT token is the Scope of the ClientApp as configured earlier

  • Create a Named value as - <aud> = api://<scope>

<policies>
   <inbound>
       <base />
<!--Check the validity of the Bearer Token-->
       <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="UnAuthorized">
           <openid-config url="https://login.microsoftonline.com/3851f269-b22b-4de6-97d6-aa9fe60fe301/.well-known/openid-configuration" />
<!--Check the claims of the Bearer Token-->
           <required-claims>
               <claim name="aud" match="all">
                   <value>{{aud}}</value>
               </claim>
           </required-claims>
       </validate-jwt>        
   </inbound>
   <backend>
       <base />
   </backend>
   <outbound>
       <base />
   </outbound>
   <on-error>
       <base />
   </on-error>
</policies>

This can be extended to check for any Optional Claims or Group Claims as well

 

  • Extract information from Token and send it backend API
    • Additional information from Bearer Token can be sent to the backend APIs

    • Backend APIs can use it for some additional decision specific to the API based on the data returned in Token

    <policies>
       <inbound>
           <base />
    <!--Check the validity of the Bearer Token-->
           <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="UnAuthorized">
               <openid-config url="https://login.microsoftonline.com/3851f269-b22b-4de6-97d6-aa9fe60fe301/.well-known/openid-configuration" />
    <!--Check the claims of the Bearer Token-->
               <required-claims>
                   <claim name="aud" match="all">
                       <value>{{aud}}</value>
                   </claim>
               </required-claims>
           </validate-jwt>
    <!--Extract AppId from Token and Add this to the request header-->
           <set-header name="appid" exists-action="append">
               <value>@{
                      string appid = "unknown";
                      string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
                      if (authHeader?.Length > 0)
                      {                        
                          string[] authHeaderParts = authHeader.Split(' ');
                          if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
                          {
                              Jwt jwt;                                                        
                              if (authHeaderParts[1].TryParseJwt(out jwt))
                              {
                                   
                                  appid = jwt.Claims.GetValueOrDefault("appid", "unknown");
                                   
                              }
                          }
                      }
                      return appid;
                  }</value>
           </set-header>
    <!--Check appid value from Request Header and take decision-->
           <choose>
               <when condition="@(context.Request.Headers.GetValueOrDefault("appid", "unknown") != "{{appid}}")">
                   <set-backend-service base-url="<error url>" />
               </when>
           </choose>
       </inbound>
       <backend>
           <base />
       </backend>
       <outbound>
           <base />
       </outbound>
       <on-error>
           <base />
       </on-error>
    </policies>

 

 

What are the different Authentication options for Clients

Authorization Code
  • Recommended for Native Mobile Apps, Web Apps, SPAs which would connect to Azure from Device or Desktop

convergence-scenarios-native.png

 

Fetch Authorization Code
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/authorize?client_id={{clientId}}&response_type=code&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient&response_mode=query&scope={{scope}}/.default&prompt=login

Key Value
client_id <Client Id> of the ClientApp
response_type code
redirect_uri https://login.microsoftonline.com/common/oauth2/nativeclient
response_mode query
scope <Scope> of the ClientApp
prompt login

 

Response
Key Value
code Authorization Code
state (Optional) state parameter if sent in the Request

 

 

Fetch Access Token and Refresh Token
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token

Key Value
client_id <Client Id> of the ClientApp
code <Authorization Code>
redirect_uri https://login.microsoftonline.com/common/oauth2/nativeclient
response_mode query
scope openid offline_access profile
grant_type authorization_code

 

Response
Key Value
token_type Bearer
scope <All Scopes>
expires_in <value>
ext_expires_in <value>
access_token <Access Token>
refresh_token <Refresh Token>
id_token (Optional) <Id Token>

 

 

Client Credentials
  • Primarily for Azure to Azure calls

convergence-scenarios-client-creds.png

 

Fetch Access Token
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token

Key Value
client_id <Client Id> of the ClientApp
client_secret <Client Secret> of the ClientApp
scope <Scope> of the ClientApp
grant_type client_credentials

 

Response
Key Value
token_type Bearer
expires_in <value>
ext_expires_in <value>
access_token <Access Token>

 

 

Device Code flow
  • Primarily for CLI based Login

v2-oauth-device-flow.png

 

Fetch Authorization Code
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/devicecode

Key Value
client_id <Client Id> of the ClientApp
scope <Scope> of the ClientApp

 

Response
Key Value
device_code Device Code
user_code Short string to identify the session
verification_uri Users need to use this URI pasting the user_code
expires_in <value>
interval <value>
message Human readable Message to the User

 

Fetch Access Token and Refresh Token
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token

Key Value
client_id <Client Id> of the ClientApp
device_code <Device Code>
grant_type urn:ietf:params:oauth:grant-type:device_code

 

Response
Key Value
token_type Bearer
scope <All Scopes>
expires_in <value>
access_token <Access Token>
refresh_token <Refresh Token>
id_token (Optional) <Id Token>

 

OpenID Connect
  • Extends the OAuth 2.0 authorization protocol

  • Single sign-on using OAuth

  • ID token - allows the client to verify the identity of the user

convergence-scenarios-webapp.png

 

Fetch Access Token, Refresh Token, ID Token
Request

https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/authorize?client_id={{clientId}}&response_type=id_token%20token&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient&response_mode=query&scope=openid&prompt=login&nonce={{value}}

Key Value
client_id <Client Id> of the ClientApp
response_type id_token token
redirect_uri https://login.microsoftonline.com/common/oauth2/nativeclient
response_mode form_post
scope openid
prompt login
nonce <value>

 

Response
Key Value
token_type Bearer
scope <All Scopes>
expires_in <value>
ext_expires_in <value>
access_token <Access Token>
refresh_token <Refresh Token>
id_token (Optional) <Id Token>

 

 

References

Posted at https://sl.advdat.com/36214j2https://sl.advdat.com/36214j2