atlas by clearpeople

How to deny external users calling your Azure AD secured API

5 July 2023
  

Recently, we had the need to deny External (also referred to as Guest) users to consume some endpoints of our Atlas API. You should be aware that if your Tenant has enabled external users, an external user can get a valid token and call your AAD secured API. This might or might not be what you want.

In our Atlas API, most of our endpoints allow external users calls, as after all, with Delegated permissions, any “on behalf of” call to other APIs, like MS Graph or SharePoint, will only allow you to do whatever you can do in there. However, we have some endpoints where we do not want to allow External users calls.

This is how we did it.

What type of token is it?

To find out if the token is coming from an external user, first we need to know if the token is a Delegated one or an Application permissions call.

There are 2 ways to do this:

  1. You can check if there is a “scp” claim in the Token. If so, this is a Delegated permissions token.
  2. Microsoft has documented another way here: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore#accepting-app-only-tokens-if-the-web-api-should-be-called-only-by-daemon-apps

Basically, if there is a “oid” and “sub” claims in the token, and both values are equal, then it is an App call:

How to deny external users calling your Azure AD secured API - App call

We are using the second option, as it appears to be the officially documented approach. However, if you go that route, you should be aware that the “sub” claim is mapped to the .net claim http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier.

In the Microsoft.Identity.Web library, there is an interesting method to return a claim from the ClaimsPrincipal. It allows you to pass multiple claim names, and it will return the value of the first claim found. The method is private, but you can copy the code:

How to deny external users calling your Azure AD secured API - Return claim

We have created a ClaimsPrincipal extension method to check if it is an App call:

How to deny external users calling your Azure AD secured API - Claimsprincipal extension

Is the token coming from an external user?

Now that we know if it is an Application or Delegated permissions call, let´s see how we can find out if the user is an external user (assuming for this example that it is a Delegated permissions token).

One of the easiest and most apparent options that comes to mind is doing an “on behalf of” call to MS Graph /me endpoint, and checking the “userType” field (Member / Guest). However, this requires another call to Graph, impacting performance (we are doing this when we want to know if a specific user, not the current one, is an external user). Ideally, we should have something in the current token telling us if the user is an external token… and luckily, it seems we have it.

According to the official documentation about the claims included in an Azure AD Access Token here: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-token-claims-reference#payload-claims, we can see this about the “idp” claim:

How to deny external users calling your Azure AD secured API - idp claim

In other words, if the “idp” claim value, is different than the “iss” claim value, it means the user is an external user.

However, as per our experience, there are a couple of gotchas here:

  1. The “idp” claim is not always present, but in that case, it just means the user is NOT an external one.
  2. When present, that claim is mapped to the .net Claim http://schemas.microsoft.com/identity/claims/identityprovider

Again, we can create a ClaimsPrincipal extension method to check this:

How to deny external users calling your Azure AD secured API - Claimsprincipal extension check

Now that we have these two extensions, we can check for external users in our endpoints, and deny calls from them:

How to deny external users calling your Azure AD secured API - External users endpoints

Create a policy for this type of user

That´s cool, but we can go a bit further and make use of the Authorization framework built in .NET.

Let´s create a custom AuthorizationHandler. First, we need to create a Requirement:

How to deny external users calling your Azure AD secured API - Authorizationhandler requirement

Now, let´s add some code to the Handler:

How to deny external users calling your Azure AD secured API - Authorizationhandler code

Next, we need to register the Handler in our Dependency Injection container and configure a new Policy to deny external users:

How to deny external users calling your Azure AD secured API - deny external users

In the code above, besides registering our custom handler, we are creating a couple of policies:

  • The first one ensures any user is authenticated.
  • The second one uses our custom handler that will deny external users, and ensure any user is authenticated.

This is to deal with the two scenario requirements: 1) any endpoint, must require that the user, is at least authenticated; and 2) some specific endpoint, besides user authenticated, will require that the user is not an external one.

As a fallback policy (the policy that will be used if no other one is required) we assign the “requireAuthUserPolicy”.

Finally, to apply the deny external users policy, we do the following:

How to deny external users calling your Azure AD secured API - Apply deny external users

And that´s all! You now have an elegant way to authorize your endpoints to non-external users only.

Hope it helps!

Author bio

Luis Mañez

Luis Mañez

Luis is Atlas Chief Architect. He is also a Microsoft 365 Development MVP and SharePoint and Cloud Solutions architect. "I help find the best technical designs to meet client needs and act as tech lead to build great solutions. I have fun with some R&D tasks, always trying to improve our tools and processes, and I often help the Microsoft community as a blogger and speaker, contributing to open-source projects."

View all articles by this author View all articles by this author

Get our latest posts in your inbox