I have a new app I am working on. It’s sort of a 1990’s style text MUD, but I’m bringing it “up to this century” with a host of new features. I’m writing the first front-end in React.
So, what does a modern MUD app look like? Well, I’m not into storing usernames and password any more, so I’m going to use a Microsoft OAuth service instead of a user database. My front end application handles state through Redux.
I’ve got a store set up as follows:
Redux is a uni-directional flow state storage system. Your application initiates actions, which are then handled by reducers that mutate the state of the application. This then causes rendering changes as a result of the change. If you don’t understand this flow, I recommend reading this tutorial.
This gets me thinking about state within the application. I need three pieces of state within my application:
- The number of network requests in flight right now. This is used to display a spinner in the title bar when there is something going on.
- The last network error that was encountered. This is used to display a warning symbol to alert the user that something went wrong.
- The current user identity.
In Redux, I need to create an action identifier and an action creator for each state change I want to use:
I can use the
store.dispatch() method to dispatch each action creator. Depending on how you organize your Redux implementation, you may have to export some and not others. As an example, for smaller apps, I tend to keep everything for one reducer or section of the state in one file, and export the actions as needed.
There are two types here -
identity type will be a new model called
This brings us to the reducer. The purpose of the reducer is to create a new version of the state that is modified according to the action. State is immutable in Redux, so you have to create a new one.
Setting up the UI
I like to abstract away the implementation details for the authentication service. This means that I don’t leak the authentication service implementation out into the rest of the application and can swap it out with a new implementation easily.
The way I do this is to design the abstraction first, then fill in the details. So, what do I need?
- Some mechanism for the user to sign in interactively.
- Action creators that sign in and out interactively.
- An initialization method to handle silent authentication.
Let’s take a look at the action creators first. I’m using
redux-thunk for asynchronous action creators. This allows me to use async/await by returning a function that takes dispatch instead of the normal action. Code will explain this better:
Make sure you add
store.dispatch(actions.initializeNetwork()) when you create your store. This will dispatch an initialization action, which will then do an asynchronous silent token acquisition.
In each of these action creators, we return a function. The
redux-thunk middleware will execute the function asynchronously, and then the various methods will get called. The net result of this is that I only have to dispatch a
signIn() action creator to get the authentication service to interactively sign in.
To initiate that, I’ve got a
SignInButton component that I can use anywhere on my UI to allow the user to sign-in or sign-out:
A couple of notes about this component (which, as React components go, is fairly straight-forward):
- I’m using React Hooks to link into the Redux store. It took me a while to figure out hooks, but once I saw it used with Redux, things fell into place. I love this syntax now since I don’t have to create extra props just to hook up the store. It feels cleaner.
- I get the authentication service name and icon from the authentication service (which we will delve into next). This sort of encapsulation means I can keep a library of authentication services elsewhere and just re-use them.
- I’m using a responsive layout. The CSS for this component has a media selector that displays the long version above a certain width (768px in my case), and the short version below that same width.
Taking a quick step back:
- There is a
SignInButtonon the page somewhere. When clicked, it triggers the
signIn()action triggers an interactive sign-in process. When the response comes back, the store is updated with the new identity.
- This causes the UI in the
SignInButtonto re-render, switching the text to
- If there is an error, the
lastErroris set in the store - I can trigger other UI components on this to display the error.
The authentication service
The only thing remaining is to create the authentication service. This needs two properties and three methods:
- Async method:
getIdentity()to retrieve the identity silently
- Async method:
signIn()to sign in interactively
signOut()to sign out
In addition, I need to set up an app registration in Azure Active Directory. Microsoft login clients are managed through Azure Active Directory. Sign into your Azure account, then go to App registrations and follow the quick start.
The only things you really need to know:
- You are implementing a web client.
- You need a redirect URI that is the same as your web app. Locally, mine is
http://localhost:3000/modernmud. Later on, it will be something else and I’ll add a redirect URI to the configuration in the Azure portal at that point.
- You need the application or client ID from the Azure portal.
This last piece of information is placed in a JSON file:
YOUR_APPLICATION_ID_HERE with the application Id of your app registration from the Azure Portal. I called this file
config.json. I’ll be using it to configure the rest of the application as well, so I don’t want it to be dedicated to MSAL.
Let’s look at the
auth-service.js which defines my authentication service:
Most of this is straight-forward, and dictated by the MSAL.js library. The one that took a little time was the
getIdentity() method. This returns an
Identity object or throws an error. We want to isolate the rest of the application from the errors that are generated by MSAL, so I have created a new error called
InteractiveSignInRequired() that I can throw.
There are two situations where this is needed:
- MSAL doesn’t have enough information to get a new token.
- MSAL doesn’t have the right permissions to get a new token.
This latter one takes some explaining. There are situations (especially in mobile web) where you can’t create an iframe to do the transport to get the token from the remote service silently. In this case, you’ll need to re-authenticate using an interactive sign-in process. The code in the
getIdentity() method handles both situations, but throws the original error if anything else happens.
This is a fair amount of code, and there is still more to do. I’ve only just managed to get an identity token, so I know about the user, but I haven’t provided any permissions to my API as yet. However, my app can now progress to the next stage.