Staeff
Staeff

Reputation: 5084

Azure Function with AD auth results in 401 Unauthorized when using Bearer tokens

I have a very simple Azure function in C# for which I've setup Azure AD Auth. I've just used the Express settings to create an App registration in the Function configuration.

public static class IsAuthenticated
{
    [FunctionName("IsAuthenticated")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "options", Route = null)]
        HttpRequest req,
        ILogger log)
    {
        return new OkObjectResult("You are " + req.HttpContext.User.Identity.Name);
    }
}

When I access the function in my browser everything works as expected (if not logged in I have to login and get redirected to my API). But if I try to access my function anywhere a Bearer token is needed I get an 401 Unauthorized error. Even weirder I also can't execute the function in the Azure Portal.

But the token was aquired without a problem and added to the request:

enter image description here

I've tried a few different things to solve this problem. First I thought maybe it's a CORS problem (since I've also had a few of those) and just set CORS to accept *, but nothing changed.

Then I've added my API login endpoints to the redirect and tried setting the implicit grant to also accept Access tokens, it's still not working.

enter image description here

Is there anything I've overlooked? Shouldn't the App registration express config just work with azure functions?

EDIT:

Putting the URL to my function app in the redirects as suggested by @thomas-schreiter didn't change anything (I've tried the config in the screenshot and also just putting each of those values on it's own).

enter image description here

EDIT 2:

I've now also tried to aquire an Bearer token the manual way with Postman, but I still run into a 401 when calling my API.

Upvotes: 22

Views: 13051

Answers (9)

Rachel
Rachel

Reputation: 1284

For me this was solved when I added the scope: [clientId]/.default per this article

Upvotes: 0

Jay Watson
Jay Watson

Reputation: 46

If you are banging your head against the wall like myself and the original poster, it may be that you are allowing users to sign in from "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)."

Note that as of May 2021, v2.0 works perfectly. If you use https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token to get a token with Postman (as described above), you will get a valid token that you can use to auth your AZ Function with.

With that said, IF a user is signed in via a personal account or an account not within your AAD, the token call made by MSAL is requested with the default Microsoft tenant id, NOT your tenant id.

THIS is why I was unable to auth my function. If you are logged in with a user in your tenant's AAD, MSAL is amazing and easy to use and everything will work as described in the documentation.

Upvotes: 2

Staeff
Staeff

Reputation: 5084

UPDATE 2020-05-12: According to ambrose-leung's answer further below you can now add a custom issuer URL which should potentially enable you to use v2 tokens. I haven't tried this myself, but maybe this will provide useful for someone in the future. (If his answer helped you please give him an upvote and maybe leave a comment 😉)


This took forever to figure out, and there is very little information about this in the offical documentations.

But it turns out the problem was/is that Azure Functions don't support Bearer tokens generated by the oauth2/v2.0/ Azure API. Since the portal uses those (if your AD supports them) you are out of luck to be able to run the function in there.

This also explains why my postman requests didn't work, because I was also using the v2 api. After switching to v1 I could access my API (Postman doesn't allow you to add a resource_id when you use the integrated auth feature, so I had to switch to handling everything manually).

After that came the realisation that you can't use MSAL either if you are writing a JS client (Angular in my case). So one alternative is ADAL, where the Angular implementation looks kind of awkward. So I decided to use angular-oauth2-oidc which took another hour of tinkering to get it to play nicely with Azure AD.

But after all that I can finally access my API.

I really don't understand why you wouldn't allow users to access Azure Function Apps with Azure AD v2 tokens, but at least this should be so much better documented. But whatever, I can finally go to sleep.

EDIT: After I opend an issue for this, they added a note that v2 isn't supported by Azure Functions, hopefully making life easier for other people.

https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad

Upvotes: 26

deemonsilva
deemonsilva

Reputation: 51

When setting up your Active Directory authentication on your Function App, set management mode to advanced and fill in the Client ID and Issuer URL as required (and the client secret if necessary).

Importantly, under the Allowed Token Audiences, enter the Application ID URI. This can be found in your registered App Registration (in your AD) under the Expose an API option.

This is what I was missing to get authentication working on my Function App. Before I added that token audience, I would always get a 401 with a valid access token.

This Azure active directory - Allow token audiences helped me get my answer but it took me a while to realise what it was referring to. Remember, it's the Application ID URI that can be found within your App Registration.

I hope it helps!

Upvotes: 5

Ambrose Leung
Ambrose Leung

Reputation: 4215

You can now use v2.0 tokens!

Instead of choosing 'Express' when you configure AAD, you have to choose 'Advance' and add the /v2.0 part at the end of the URL.

Choose Advanced

This is the code that I use in my console app to present the user with a login prompt, then take the bearer token for use with the Azure Function.

string[] scopes = new string[] { "profile", "email", "openid" };
string ClientId = [clientId of Azure Function];
string Tenant = [tenantId];
string Instance = "https://login.microsoftonline.com/";
var _clientApp = PublicClientApplicationBuilder.Create(ClientId)
    .WithAuthority($"{Instance}{Tenant}")
    .WithDefaultRedirectUri()
    .Build();
var accounts = _clientApp.GetAccountsAsync().Result;

var authResult = _clientApp.AcquireTokenInteractive(scopes)
            .WithAccount(accounts.FirstOrDefault())
            .WithPrompt(Prompt.SelectAccount)
            .ExecuteAsync().Result;
var bearerTokenForAzureFunction = authResult.IdToken;

Upvotes: 6

Rahul Ruikar
Rahul Ruikar

Reputation: 1086

I managed to get it working through postman using following configuration. Important lesson was setting in "Allowed token audiences" and "resource" name used in postman to acquire token should be same in this case. I used the same code provided here in question. in this case app registered in Azure AD is a client and resource as well. configuration and testing through postman as follows

enter image description here

Acquire token in postman

enter image description here

Calling azure function using Postman .. Authorization header with bearer token

enter image description here

Upvotes: 13

typheon
typheon

Reputation: 158

I'm facing the exact same issue today. The issue turned out to be the resource id that I was passing when requesting the access token.

For example, initially I was requesting a token like this, using the function URL as the resource id:

AuthenticationResult authenticationResult = authenticationContext.AcquireTokenAsync("https://myfunction.azurewebsites.net", "myClientAppIdGUID", new Uri("https://login.live.com/oauth20_desktop.srf"), new PlatformParameters(PromptBehavior.SelectAccount)).Result;

While this returned an access token, I was receiving a 401 unauthorized when using the access token to call my function api.

I changed my code to pass my function apps App Id as the resource:

AuthenticationResult authenticationResult = authenticationContext.AcquireTokenAsync("myFunctionAppIdGUID", "myClientAppIdGUID", new Uri("https://login.live.com/oauth20_desktop.srf"), new PlatformParameters(PromptBehavior.SelectAccount)).Result;

Everything works fine now.

Upvotes: 1

Imran Arshad
Imran Arshad

Reputation: 4002

The only thing i can think of right now is Allowed Audience.

Go to Your Active directory settings and click Advance. Under Allowed Token Audience Add your exact function url. It might already be there with call back url but Simply replace it with only function base url without any call back as mentioned in the picture.

Make sure when you press ok , you also save your Authentication / Authorization setting to take effect and try again after 1min or so. I tested using PostMan and passing bearer token and it works !

enter image description here

Upvotes: 1

Thomas Schreiter
Thomas Schreiter

Reputation: 810

In the AAD app itself, go to Settings -> Reply URLs and verify that the url of the Function App is in the list, which has the following format: https://mycoolapp.azurewebsites.net. If it isn't, then add it.

If you use slots, you have to add it for both slots.

Upvotes: 1

Related Questions