This article shows how to call Azure AD protected Functions from Single Page Application (SPA). The task seems trivial, but you can easily spend days finding up-to-date instructions. This article is updated 14.10.2020 to use the MSALv2 library and the conventions on how to register your apps on Azure AD to get this working.
Solution overview
I needed to call Azure AD-protected Functions from my Single Page App (SPA).
The key point in solution is to create two app registrations linked together. One for the function app, which Exposes the API, and the other one for your frontend single page application that uses the API. Both are configured to use Azure AD for authentication. Your create these app registrations on your Azure AD.
The idea is somewhat difficult to grasp, and LOTS of details have to go right in order the construct to work. The best explanation for the pattern can be found on page Tutorial – Enable your Web Apps to sign-in users and call APIs with the Microsoft identity platform for developers. This explained all the different patterns used in authentication. Finding the Call your own API – example shows exactly how the app registrations should be done using the “frontend calls backend using on-behalf-of pattern”. The example guides you to create this kind of construct, that will also work also with our single page javascript apps:
Image: Single Page App, Functions and App registrations.
Sample code
I’ve created a small github repo for the frontend single page application, this should get you started on the topic. The repo contains samples for three scenarios:
-
- Basic login with nothing else
- Login and calling MS Graph “me” function to get user details like name.
- Login and calling my own Azure AD protected function.
Notice that you’ll need a web server to test these SPAs – you can’t get them to work from local files. This is because Azure authentication REQUIRES you to enter a redirect uri (or multiple uris), where you are allowed to return after authentication. The samples are simplified from this Microsoft example: MSAL.js 2.x Vanilla JavaScript Single-page Application
Tasklist in short
Here’s an overview of what you need to do:
- Create & deploy your azure function. Test it without authentication first.
- Guide: Configure your Azure Functions app to use Azure AD login.
- Apply the advanced configuration section from the above linked guide.
- Expose an API
- Turn on AAD authentication in your function’s app service plan.
- Create an app registration for your SPA
- Guide: Register a new application on Azure Portal. You’ll be using the MSAL.js 2.0 with auth code flow.
- The redirect URIs: This is the uri (or uris) where you want to return after the login redirect flow has completed.
- Give your SPA app registration rights to call your function app.
- You have already “Exposed an API” in the function app registration…
- Next “Use API” on your frontend app registration.
- Configure your sample application to use the registration info you created.
- Done!
Let’s focus on the difficult parts.
Function App Registration
Follow the instructions on this page: Configure your Azure Functions app to use Azure AD login.
While all steps described are important, I’d pinpoint the following:
- The redirect uri:s, put them exactly as described in the documentation, replace the url start with your own function’s https address. For example, https://myfunctionapp.azurewebsites.net/.auth/login/aad/callback
- Expose the API makes the api available for other applications to use. The scope part is important. You’ll need the scope defined here when creating the frontend app registration. And later on you need to copypaste this scope to your javascript as well. Scope is the full string, for example, https://myfunctionaddress/user_impersonation – see image below.
Image: Expose an Api & Scope Name
The above string in whole is your Scope – you’ll need it on frontend app registration, and you need to put in your spa code.
Register your frontend app, use an API
Next you need to create the second app registration in Azure AD, now for your frontend application. First, follow these instructions: Register a new application, use the recommended MSAL.js 2.0 with auth code flow. After this your app registration is ready for handling logins, but it can’t call your functions yet – we’ll configure that next.
Add permission to call your function app
Now we’ll give your frontend app rights to call the API you exposed in the function app registration.
Once you have created new App registration for your SPA (mine was named CoreApp), go to Manage / API permissions on app registration. From there, + Add a permission.
Image: Add Permission to your frontend app.
Next find and pick your function app registration (mine was called anothervApp). Select “user_impersonation” and Add Permission.
Image: Select “user_impersonation” and add permission.
Optional last step: Grant Admin consent. This means that each individual user doesn’t have to accept the consent – your frontend application calls your backend application on behalf of the user. You as an administrator can pre-accept this for everyone.
Image: Grant admin consent if you wish. Optional.
Congratulations! Now the app registrations should be ok. Let’s move on to the actual application.
Configure the values into the sample application
Clone my sample app repo. The examples contains three separate applications for most typical use cases. I suggest you first test that your login works; try the plainlogin directory sample. Then head to functioncall directory, which contains the sample to call your own function.
authConfig.js
This file links your application the frontend app registration you created. Replace the following:
- Frontend app registration clientId. Get this from your app registration, it can be found on registration overview page, Application (client) ID
- authority, pick either:
- If you allow any microsoft account for login (personal, work, xbox, hotmail, any), the authority will be https://login.microsoftonline.com/common/
- If you only allow users from your personal AD to login, the authority is in format: https://login.microsoftonline.com/tenant_id. You get your AD tenant id from the app registration overview page, under Directory (tenant) ID
- redirectUri is your app URL where to return after the Microsoft login redirect flow has completed. In my samples, we always want to return to the same page we left from. So in my example, the return uri is for example:
- https://core.jannehansen.com/spa/plainlogin
- https://core.jannehansen.com/spa/graphapi
- https://core.jannehansen.com/spa/functioncall
One very important thing. The redirectUri must match PRECISELY the url you entered in frontend app registration. Otherwise this won’t work. Use your own return addresses, depending on how and where you installed the sample apps. But the idea is to return to the same page you left from.
authRedirect.js
This file contains:
- The actual token acquisition process & login implementation. This is marked top part of the file, you shouldn’t need to change anything here in your own implementation.
- graphapi example: Calling MS GraphAPI (marked on bottom of file)
- functioncall example: Calling your own AD-protected function (marked also on the bottom of the file)
when you test plainlogin or graphapi call, you shouldn’t need to change anything on authRedirect.js file – should work as is.
For the functioncall example,
- first change the const functionCallRequest scope to match yours…
- …then change function endpoint to match your function’s address.
- And depending on what your function does, change the input/output parameters for the actual call in
function callMyFunction(accessToken). My test function used GET, with single name parameter, and then returned “Hello [name]!” as response body. Your’s will most likely do something else, change this call accordingly.
Problems? 401- Forbidden?
To shorten the story: The problem is most likely in the App Registrations. Make sure the app registrations are done EXACTLY as stated in the MS documentation. redirect uri’s – clientid, tenant, scope definitions etc.
This is pretty much it. Now you should be able to call your AD-protected functions. As you saw, lots of details has to go right in order to make this work. After finding the right document(s), the implementation usually takes only few minutes.
This method works now at the time of writing. Please notify me when it doesn’t work anymore. Until that – Enjoy!
Great Article. It works. I successfully done the setup following this article.
If you’re using Azure Functions v4 and after following this guide keep getting 401/403 errors on your API requests, place the Application ID URI (The one beginning with api:// that you created on the Expose an API screen) of the Backend application, in the ‘Allowed Token Audiences’ section of the Identity Provider on the Azure Function App (Authentication > Edit identity Provider)
This little gotcha cost me 4 hours of debugging!
Hi Janne
Nice Article.
I have a App Service (Webapp) hosting a SPA protected by AAD.
I have a Azure Function protected by AAD too
I want my SPA call my Azure Function (the user will then be logged in).
Do I need to implement your workflow ? Naively, i hoped that the user was authenticated on the SPA so can call the Azure Functions because already authenticated ?
Best
Hi and sorry for Late reply, didn’t notice the comment earlier.
Yes. This is the workflow you need to implement. Spa handles the login and gets you the token you then need to call the function. And you need to get the app registrations right too.
Br,
Janne
Great. I had spend days on this reaching nowhere. Thanks for this really helpful article, the concept is clear.
I also wanted add that, in some organisations, the optional last step for grand admin consent is not enabled. In those organisation, in api permission tab, even when the admin consent required is marked as No, the frontend app requests for admin consent as “This app requires your admin’s approval to” asking justification. The approval goes to some predecided approvers in the organisation.
The plainlogin works. Not sure why admin approval is required for using one application api from another preapproved app.
Is this article still relevant, as I noticed it hasn’t been updated in almost a year and the Azure portal appears to have changed a bit in the past few months, making most tutorials and articles I’m finding out of date now?
I’m also wondering if the steps are the same when using Azure AD B2C? Every article I find talks about Azure AD, which assumes you just want to build an internal app for your organisation. But what about customer facing apps that use B2C?
Hi! Screenshots most likely get outdated quite fast, but the method is still the same as of my knowledge. I haven’t checked if this method works with B2C, so I’d say that this works for internal apps (using single azure tenant) or if you allow any tenant (the common).
Thanks! This was the first search result and a perfect match to my question! Appreciate the clear and concise steps and screenshots!
Thanks!
How would I set up the function app to call other APIs using on-behalf-of flow? I want to exchange the access token passed by the SPA in the function app for access tokens to other APIs (The function app has the required permissions to do that). What scope do I need for that?
Haven’t done this particular use case if I understood correctly. Just to make sure – you have a single page app calling Azure function, and then you want the function to call yet another api with the same credentials?
Thanks for a great article, finding info about this has proved to be a challenge.
However, I had a great deal of head scratching while trying to get this right. I managed to get an access token but when I tried to use it, my azure function kept responding with a 401.
After examining the access token I realized that the audience was set to the same as the name of the exposed API, so by adding the name (Application ID URI) of the exposed API to the “allowed audiences” when setting up the authorization of the Azure Functions App I finally managed to get this working.
I hope this will help you if you stumble upon the same issue as me.
Regards//Joakim