DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Graph API for Entra ID (Azure AD) Object Management
  • Develop Microservices Using Azure Functions, API Management
  • Zero to AI Hero, Part 3: Unleashing the Power of Agents in Semantic Kernel
  • Exploration of Azure OpenAI

Trending

  • Why Documentation Matters More Than You Think
  • Optimize Deployment Pipelines for Speed, Security and Seamless Automation
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  • Start Coding With Google Cloud Workstations
  1. DZone
  2. Data Engineering
  3. Databases
  4. Using ADFS With Azure API Management

Using ADFS With Azure API Management

A DZone MVB explores some issues he ran into while trying to use these two technologies to create an API and push it online.

By 
Anders Abel user avatar
Anders Abel
·
Jan. 05, 18 · Analysis
Likes (1)
Comment
Save
Tweet
Share
10.6K Views

Join the DZone community and get the full member experience.

Join For Free

Azure API Management is an API gateway that can be used to publish APIs to the Internet. It provides features such as per-developer API keys, request throttling, and request authentication. One of the ways requests can be authenticated is through standard OAuth2 bearer tokens. I assume that the most common scenario is to use Azure AD to issue those tokens. But if an organization is not that cloud-enabled yet and the users are in an on-prem AD, the natural token issuer is to use ADFS. And ADFS on Windows Server 2016 supports OpenID Connect, so it should work, right?

Well, it turns out it didn't just work. The OpenID Connect implementation in ADFS has some quirks that need to be handled. In the end, it worked, but with some limitations.

Issuer and Access Token Issuer

One of the neat things with OpenID Connect is that it provides a metadata-based convention for configuration. There's no need to download and handle certificates to register signing keys, it generally just works. Until it doesn't. Which was the case here.

First, the configuration in the Azure API Management Policy was fairly straightforward. The policy checks that a matched query string parameter colour from the public facing URL is also present as a claim. This carries all the way to the active directory user object, where the "other pager" field was used to list the colors that a certain user is allowed to use in the URL to the API.

<policies>
  <inbound>
    <validate-jwt header-name="Authorization">
      <openid-config url="https://adfs.example.org/adfs/.well-known/openid-configuration" />
      <required-claims>
        <claim name="colour" match="all">
          <value>@((string)context.Request.MatchedParameters["colour"])</value>
        </claim>
      </required-claims>
    </validate-jwt>
    <base />
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
  </outbound>
  <on-error>
    <base />
  </on-error>
</policies>

This code turned out to work in the end, after some workarounds had been applied.

ADFS, Audiences, and the Resource Parameter

The first problem was obvious when I used jwt.io to inspect the access token I received from the ADFS. It didn't contain the requested colours scope and didn't contain the colours claims.

{
  "aud": "urn:microsoft:userinfo",
  "iss": "https://adfs.example.org/adfs/services/trust",
  "iat": 1511714437,
  "exp": 1511718037,
  "apptype": "Public",
  "appid": "8059f5ed-fa9b-4165-815c-663dec49b965",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified",
  "auth_time": "2017-11-26T16:40:36.000Z",
  "ver": "1.0",
  "scp": "openid",
  "sub": "r5PvRoaOXFmaJ+q6LyeVslYVXZl38F/UkrBvQNlyoY8="
}

Apparently, ADFS has added a non-standard parameter resource that must be supplied in the token request to get an access token aimed for an API. The default access token as returned above is only meant for the user info endpoint on the ADFS server. With a resource parameter added, I got a better access token. It now includes the colours scope and the ADFS issuance transform rules for the Web API now kicks in and includes the colour claim in the access token. Note that it now also has a different audience - the identifier of the API.

{
  "aud": "https://my-example-api.azure-api.net/colours",
  "iss": "https://adfs.example.org/adfs/services/trust",
  "iat": 1511718387,
  "exp": 1511721987,
  "colour": "Blue",
  "apptype": "Public",
  "appid": "8059f5ed-fa9b-4165-815c-663dec49b965",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified",
  "auth_time": "2017-11-26T17:46:24.000Z",
  "ver": "1.0",
  "scp": "colours"
}

So it turns out that ADFS is issuing different access tokens for different APIs and the way to request an access token for a specific API is to use the non-standard resource parameter. I'll write some more on this in another post.

For now, just let's get on with the work and try to use the access token to access the API.

ADFS and acces_token_issuer 

During first try with my new access token, things just didn't work. Finding out why wasn't obvious. I copied the access token I had got in my client application into the API test tools in the Azure portal to get a trace. The trace just reveals that the JWT validation failed. To get the actual JWT validation error, one has to follow the link that's listed in the trace. In that log, the error message is clear (kudos to the Microsoft dev who decided to include the actual values in the exception message).

message: "JWT Validation Failed: IDX10205: Issuer validation failed. Issuer: 'http://adfs.example.org/adfs/services/trust'. Did not match: validationParameters.ValidIssuer: '' or validationParameters.ValidIssuers: 'https://adfs.example.org/adfs'

Now, what happened here? Looks like the iss field of the JWT doesn't match the one listed in the OpenID Connect configuration information. Looking at the ADFS OpenID Connect configuration information available at https://adfs.example.org/adfs/.well-known/openid-configuration showed another non-standard OpenID Connect behavior of ADFS. At the top, an issuer value of https://adfs.example.org/adfs is shown. This also corresponds to the OpenID Connect standard that the configuration document path is formed by concatenating the issuer URL with /.well-known/openid-configuration. The id_token correctly contains https://adfs.example.org/adfs as the issuer. However, that's not the issuer found in the access token. The access token uses the ID I've set up in ADFS as the Federation Service Identifier. That's the value used as the Entity ID in SAML-based tokens. And that identifier is actually present in the metadata in the non-standard field access_token_issuer.

{
  "issuer": "https://adfs.example.org/adfs",
  "authorization_endpoint": "https://adfs.example.org/adfs/oauth2/authorize/",
  "token_endpoint": "https://adfs.example.org/adfs/oauth2/token/",
  "jwks_uri": "https://adfs.example.org/adfs/discovery/keys",
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "client_secret_basic",
    "private_key_jwt",
    "windows_client_authentication"
  ],
  "response_types_supported": [
    "code",
    "id_token",
    "code id_token",
    "id_token token",
    "code token",
    "code id_token token"
  ],
  "response_modes_supported": [
    "query",
    "fragment",
    "form_post"
  ],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "client_credentials",
    "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "implicit",
    "password",
    "srv_challenge"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "scopes_supported": [
    "aza",
    "logon_cert",
    "user_impersonation",
    "winhello_cert",
    "profile",
    "email",
    "allatclaims",
    "vpn_cert",
    "openid",
    "colours"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "token_endpoint_auth_signing_alg_values_supported": [
    "RS256"
  ],
  "access_token_issuer": "http://adfs.example.org/adfs/services/trust",
  "claims_supported": [
    "aud",
    "iss",
    "iat",
    "exp",
    "auth_time",
    "nonce",
    "at_hash",
    "c_hash",
    "sub",
    "upn",
    "unique_name",
    "pwd_url",
    "pwd_exp",
    "sid"
  ],
  "microsoft_multi_refresh_token": true,
  "userinfo_endpoint": "https://adfs.example.org/adfs/userinfo",
  "capabilities": [],
  "end_session_endpoint": "https://adfs.example.org/adfs/oauth2/logout",
  "as_access_token_token_binding_supported": true,
  "as_refresh_token_token_binding_supported": true,
  "resource_access_token_token_binding_supported": true,
  "op_id_token_token_binding_supported": true,
  "rp_id_token_token_binding_supported": true,
  "frontchannel_logout_supported": true,
  "frontchannel_logout_session_supported": true
}

Apparently, not even Microsoft's own API Management platform knows about that field. So the incoming access token is rejected by Azure API Management due to issuer names not matching. First, I tried to solve that by manually adding the access token issuer value in the API Management policy, but I never got it working (I think it was something with an incorrect trailing space added). Instead, I went back and renamed my ADFS server, so that the Federation Service Identifier now is https://adfs.example.org/adfs (the ADFS service needs a restart for the rename to take effect). That gives a new OpenID Connect configuration document where the issuer and access_token_issuer fields are the same.

Finally, my JWT validation works.

Solving this by renaming the ADFS server identifier is nothing that can easily be done in an existing federation setup. All other applications (relying parties) and upstream identity providers (claims providers) must, of course, be updated with the new federation service ID.

Conclusion

Using ADFS as an OAuth2 token issuer for Azure API Management kind of works. A workaround is required to handle the issuer vs. access_token_issuer issue. In a fresh ADFS setup that's possible through a rename. In an existing environment probably not.

What's more severe is that to get the access token the extra resource parameter must be added. Microsoft's OpenID Connect handler for ASP.NET Core 2 supports that and their ADAL.js library for JavaScript does. But other standard-conforming libraries such as Brock Allen's oidc-client.js or libraries for non-.NET server-side applications won't work. And the entire purpose of the API Management platform is to publish APIs on the Internet for other developers to use. Using an OpenID Connect Provider that requires non-standard behavior of the client will inevitably create compatibility issues in such a scenario.

API azure

Published at DZone with permission of Anders Abel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Graph API for Entra ID (Azure AD) Object Management
  • Develop Microservices Using Azure Functions, API Management
  • Zero to AI Hero, Part 3: Unleashing the Power of Agents in Semantic Kernel
  • Exploration of Azure OpenAI

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!