Web Development
Checking Auth0 Roles and Permissions with a React SPA

Checking Auth0 Roles and Permissions with a React SPA

With Auth0 you can set up a completely SAAS-based Authentication and Authorization provider, freeing you of any worries surrounding losing passwords, implementing the latest authentication best practices and a whole heap of other things.

One thing that tripped me up with it, however, was checking what permissions have been assigned to my users.

In my setup, I was using a .NET Core Web API hosted in Azure and a React SPA hosted separately. My API would need to check the permissions assigned to my users in order to check they could perform actions such as updating another user, and my React front end would need to show and hide various pieces of functionality to match.

Checking Permissions in the .NET Core Web API

This article isn't about permissions in APIs but it's important to understand how this differs too what's needed in the React front end.

The tutorial ASP.NET Core Web API v2.1: authoization is relatively straightforward to follow and at the end, you will have an API that performs Authorization with a user's permissions.

Permissions in Auth0 are assigned to users via roles. i.e. A user gets assigned a role and roles have related permissions. This way we can design our application around granular permissions and the roles which give those permissions to a user can be configured separately.

The permissions are provided to the API within the user's authentication token, however for this to work they actually have to be in the token to start with. By default roles and permissions are not included in a token.

These can be included by setting the scope property when calling getAccessTokenSilently (the function to get a token) in React.

1const token = await getAccessTokenSilently({
2 authorizationParams: {
3 audience: 'https://api.example.com/',
4 scope: 'read:posts',
5 },
6});
7const response = await fetch('https://api.example.com/posts', {
8 headers: {
9 Authorization: `Bearer ${token}`,
10 },
11});

Alternatively, you can Enable Role-Based Access Control for APIs. This will increase the size of your token by including all permissions assigned to the person, but removes the need to request them.

Checking Permissions in React

It's likely that before calling an API you will want your React logic to check the users permissions. After all, it wouldn't be a great user experience if they're allowed to perform actions which result in permission errors.

As well as the getAccessTokenSilently the useAuth0 React Hook contains a property for the user and getIdTokenClaims. When using these you will be able to see the details associated with the logged in user. e.g. Name. However unlike getAccessTokenSilently, it doesn't matter what you include in the scope or if you've enabled role-based access control, the properties will not include what permissions or roles are assigned to the user.

To include them you must setup a login action to add them to the token. See Add user roles to ID and Access tokens in Auth0's documentation.

This will direct you to create a login flow where you add a custom action containing the following.

1/**
2 * @param {Event} event - Details about the user and the context in which they are logging in.
3 * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
4 */
5exports.onExecutePostLogin = async (event, api) => {
6 const namespace = 'https://my-app.example.com';
7 if (event.authorization) {
8 api.idToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
9 api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
10 }
11}

Note, the namespace part is so that what you add to the token is unique and doesn't clash with anything Auth0 is adding.

You can now follow these examples to perform a claims checks on the user. E.g.

1import { User } from '@auth0/auth0-react';
2
3function checkClaims(claim?: User, role?: string) {
4 return claim?.['https://my-app.example.com/roles']?.includes(role);
5}
6
7export default checkClaims;

This as you may have spotted is only assigning a role to the token rather than actual permissions. In my case, this was enough, however, this is one area I think the react hook is severely lacking.

Writing custom logic to set a users permissions into the set of claims feels highly wrong, but after some back and forth with Auth0's support team, for now though this appears to be the only option.

Parallel API calls in JavaScript

Parallel API calls in JavaScript

Although JavaScript is single-threaded, it is relatively simple to avoid blocking the thread by writing code in an asynchronous style through the use of functions with an async keyword that returns a promise and only awaiting when you need it.

For example, the following code contains an API call in an async function.

1 private async CallAPI() {
2 const response = await axios.get('https://fooapi.com/Films')
3
4 console.log(response.data)
5 }

If we wanted to call two APIs then we could do the following.

1 private async CallAPI() {
2 const response = await axios.get('https://fooapi.com/Films')
3 const response2 = await axios.get('https://fooapi.com/Music')
4
5 console.log(response.data)
6 console.log(response2.data)
7 }
8

However because both API calls are being immediately awaited, this will result in the execution of the function stopping while it waits for the result of the first API call before making the second.

As we don't need that response to make the second API call, we can improve the performance of the code by moving the await keywords to the point we actually need the responses. Both API calls will now be made in parallel rather than the second waiting for the first to finish.

1private async CallAPI() {
2 const response = axios.get('https://fooapi.com/Films')
3 const response2 = axios.get('https://fooapi.com/Music')
4
5 console.log(await response.data)
6 console.log(await response2.data)
7}

Call an API for each item in an array in parallel

Those initial examples were quite easy to understand, but what if we have a scenario where you get a list of films showing at the cinema on a particular day and display the show times for each? You have two APIs at your disposal; The first returns all the films and the second returns the showtimes for a given film.

To get all the data you need you first need to call the API to get the films and then loop through them to call the second API and get the show times.

1interface Film {
2 id: string
3 name: string
4 showtimes: ShowTime[]
5}
6
7interface ShowTime {
8 date: Date
9}
10
11class FilmService {
12 public async GetShowtimes(): Promise<Film[]> {
13 const filmResponse = axios.get('https://fooapi.com/Films')
14
15 const films: Film[] = []
16 for (let e of (await filmResponse).data) {
17 console.log("Get Showtimes")
18 const showTimes = await axios.get(
19 `https://fooapi.com/Films/${e.id}/ShowTimes`
20 )
21 console.log("Got Showtimes")
22
23 films.push({
24 id: e.id,
25 name: e.name,
26 showtimes: showTimes.data,
27 })
28 }
29
30 return films
31 }
32}
33
34// Output
35// Get Showtimes
36// Got Showtimes
37// Get Showtimes
38// Got Showtimes
39// Get Showtimes
40// Got Showtimes

The problem with this code though is that its execution length will be the films API call response time + (showtime API response time * the number of films).

As each call to the second API have no dependencies on each other, it doesn't make any sense for them to have to be performed sequentially in the loop.

Instead, we can refactor for loop to be a map calling an async function which returns the new array and make use of Promise.all() to ensure they have all completed before returning the results.

1interface Film {
2 id: string
3 name: string
4}
5
6interface FilmWithShowtime extends Film {
7 showtimes: ShowTime[]
8}
9
10interface ShowTime {
11 date: Date
12}
13
14class FilmService {
15 public async GetShowtimes(): Promise<FilmWithShowtime[]> {
16 const filmResponse = await axios.get('https://fooapi.com/Films')
17
18 const films: FilmWithShowtime[] = await Promise.all(
19 filmResponse.data.map(async (e: Film) => {
20 return await this.getFilmWithShowtimes(e)
21 })
22 )
23
24 return films
25 }
26
27 private async getFilmWithShowtimes(film: Film): Promise<FilmWithShowtime> {
28 console.log("Get Showtimes")
29 const showTimes = await axios.get(
30 `https://fooapi.com/Films/${film.id}/ShowTimes`
31 )
32
33 console.log("Got Showtimes")
34
35 return {
36 id: film.id,
37 name: film.name,
38 showtimes: showTimes.data,
39 }
40 }
41}
42
43// Output
44// Get Showtimes
45// Get Showtimes
46// Get Showtimes
47// Got Showtimes
48// Got Showtimes
49// Got Showtimes
50

The API calls for show times will now app be made in parallel reducing the overall execution time of the code.

Making your code neater with TypeScript Paths

Making your code neater with TypeScript Paths

When I started doing development with TypeScript one of the first things that irked me was all those relative paths in import statements.

Code organisation and creating layers for maintainability is one of the things I'm most OCD about, so things like a service to call Prismics API end up in a folder called services. However, to then use it in a NextJs page a few folders deep ends up with an import statement such as this.

1import { prismicService } from '../../../src/services/Prismic/Prismic.service'

Yuck. Look at all those ../../ and to make things worse this also means if you move a file its no longer pointing in the right place.

Fortunately, there is a solution. Typescript supports path mapping through the tsconfig.json file.

First set a base URL property in the compiler options.

1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 }
5}

At this point, you could get rid of all those ../ and just start at the src. e.g.

1import { prismicService } from 'src/services/Prismic/Prismic.service'

This is a lot better and we can now move files around, but it's still not perfect. The fact it's in a folder called src is a bit irrelevant for my liking.

Using the path's property we can define a grouping even lower down. e.g.

1// tsconfig.json
2{
3 "compilerOptions": {
4 "baseUrl": ".",
5 "paths": {
6 "@Services/*": ["src/services/*"]
7 }
8 }
9}
10
11// New import
12import { prismicService } from '@Services/Prismic/Prismic.service'

For more information on TypeScript paths check out the official documentation here.

Going a bit further

As well as just making your code a bit easier to read. This also makes a simple solution to swapping out code in a way that would otherwise need a dependency injection framework.

Coming from a .net background where dependency injection and interfaces are frequently used there can be a desire to do it with TypeScript too. Although it is possible (you can read my blog post on dependency injection with NextJs and Tsyringe here) it has quite a heavy overhead to the amount of code you need to write.

A simpler solution can be like what Vercel have done in NextJs commerce for the different providers.

Check out this part of their tsconfig file.

1 "paths": {
2 "@lib/*": ["lib/*"],
3 "@utils/*": ["utils/*"],
4 "@config/*": ["config/*"],
5 "@assets/*": ["assets/*"],
6 "@components/*": ["components/*"],
7 "@commerce": ["../packages/commerce/src"],
8 "@commerce/*": ["../packages/commerce/src/*"],
9 "@framework": ["../packages/local/src"],
10 "@framework/*": ["../packages/local/src/*"]
11 }

That @framework section at the end defaults to local, but gets swapped out to a commerce provider like bigcommerce or shopify. As each one implements the same methods, anything importing from @framework simply uses the correct code when the solution is built.

Combining GROUP BY and CASE in a Linq Query

Combining GROUP BY and CASE in a Linq Query

With SQL Server you have the ability to make changes to the raw table field data within a GROUP BY clause by using a CASE statement. e.g.

1SELECT [QuestionId]
2 ,CASE WHEN Answer LIKE 'Other%' THEN 'Other'
3 ELSE Answer END AS [Answer]
4 ,count(*) as Count
5 FROM [SurveyResults-Q4-2022]
6 GROUP BY QuestionId,
7 CASE WHEN Answer LIKE 'Other%' THEN 'Other'
8 ELSE Answer END

When using EF Core though rather than writing SQL directly we write Linq queries which get translated to the appropriate SQL query.

To achieve a CASE statement in a GROUP BY you can write the following.

1var q = from r in _context.SurveyResultsQ42022s
2 group r by new { r.QuestionId, Answer = (r.Answer.StartsWith("Other") ? "Other" : r.Answer) }
3 into g
4 select new { g.Key.QuestionId, g.Key.Answer, Count = g.Count() };
Tagged: