In a previous post, we created a static web app that retrieves documents from Cosmos DB via an Azure Function. The Azure Function got deployed automatically and runs off the same domain as your app. In essence, that frees you from having to setup Azure Functions separately and configuring CORS in the process.
Instead of allowing anonymous users to call the api at https://yourwebapp/api/device, I only want to allow specific users to do so. In this post, we will explore how that works.
You can find the source code of the static web app and the API on GitHub: https://github.com/gbaeke/az-static-web-app.
More into video tutorials? Then check out the video below. I recommend 1.2x speed! 😉
Create a routes.json
To define the protected routes, you need routes.json in the root of your project:

The routes.json file serves multiple purposes. Check out how it works here. In my case, I just want to protect the /api/* routes and allow the Authenticated users role. The Authenticated role is a built-in role but you should create custom roles to protect sensitive data (more info near the end of this post). For our purposes, the platform error override is not needed and be removed. These overrides are useful though as they allow you to catch errors and act accordingly.
Push the above change to your repository for routes.json to go in effect. Once you do, access to /api/* requires authentication. Without it, you will get a 401 Unauthorized error. To fix that, invite your users and define roles.
Inviting Users
In Role Management, you can invite individual users to your app:

Just click Invite and fill in the blanks. Inviting a user results in an invitation link you should send the user. Below is an example for my Twitter account:

When I go to the invite link, I can authorize the app:

After this, you will also get a Consent screen:

When consent is given, the application will open with authentication. I added some code to the HTML page to display when the user is authenticated. The user name can be retrieved with a call to .auth/me (see later).

In the Azure Portal, the Twitter account is now shown as well.

Note: anyone can actually authenticate to your app; you do not have to invite them; you invite users only when you want to assign them custom roles
Simple authentication code
The HTML code in index.html contains some links to login and logout:
- To login: a link to /.auth/login/github
- To logout: a link to /.auth/logout
Microsoft provides these paths under /.auth automatically to support the different authentication scenarios. In my case, I only have a GitHub login. To support Twitter or Facebook logins, I would need to provide some extra logic for the user to choose the provider.
In the HTML, the buttons are shown/hidden depending on the existence of user.UserDetails. The user information is retrieved via a call to the system-provided /.auth/me with the code below that uses fetch:
async getUser() {
const response = await fetch("/.auth/me");
const payload = await response.json();
const { clientPrincipal } = payload;
this.user = clientPrincipal;
user.UserDetails is just the username on the platform: gbaeke on GitHub, geertbaeke on Twitter, etc…
The combination of the routes.json file that protects /api/* and the authentication logic above results in the correct retrieval of the Cosmos DB documents. Note that when you are not authorized, the list is just empty with a 401 error in the console. In reality, you should catch the error and ask the user to authenticate.
One way of doing so is redirecting to a login page. Just add logic to routes.json that serves the path you want to use when the errorType is Unauthenticated as shown below:
"platformErrorOverrides": [
{
"errorType": "NotFound",
"serve": "/custom-404.html"
},
{
"errorType": "Unauthenticated",
"serve": "/login"
}
]
The danger of the Authenticated role
Above, we used the Authenticated role to provide access to the /api/* routes. That is actually not a good idea once you realize that non-invited users can authenticate to your app as well. As a general rule: always use a custom role to allow access to sensitive resources. Below, I changed the role in routes.json to reader. Now you can invite users and set their role to reader to make sure that only invited users can access the API!
"routes": [
{
"route": "/api/*",
"allowedRoles": ["reader"]
}
]
Below you can clearly see the effect of this. I removed GitHub user gbaeke from the list of users but I can still authenticate with the account. Because I am missing the reader role, the drop down list is not populated and a 401 error is shown:

Conclusion
In this post, we looked at adding authentication and authorization to protect calls to our Azure Functions API. Azure Static Web Apps tries to make that process as easy as possible and we all now how difficult authentication and authorization can be in reality! And remember: protect sensitive API calls with custom roles instead of the built-in Authenticated role.