I recently started working on a React project and was looking to add authentication support to it. As a long time Ember developer I expected there to be a standard community solution similar to ember-simple-auth that I would be able to install and extend for my use cases. However, as I started searching on community areas, github, and npm and I didn’t find anything quite like what I was looking for. I ended up building my own solution based on the principles behind Ember-Simple-Auth hence the name of React-Simple-Auth. However, Ember-Simple-Auth is full fledged production ready ember-addon where as mine is just an example of what could be. It’s not exactly apples to apples here, but it’s a great name. If it turns out that it works well I imagine it could be separated out into an standalone npm package. In this article I’ll focus on explaining the details of the end-to-end solution and hope to provide a helpful resource since I saw gaps in the existing resources out there. Hopefully by walking through all the requests, methods calls and integration with react from the user clicking login to sending a request using an access token this will give you ideas about how you could use this in your own applications or build your own similar service.
Below I describe the notable resources I found and reasons I don’t think they’re sufficient. If you’d prefer to skip to the explanation of React-Simple-Auth go to the next section or go straight to the code:
This seemed fairly thorough, but shows manually entering user name and password and which is not very applicable. Most sites will use federated login from a third party as you don’t want to be in the business of handling user credentials. It was also a tutorial specifically written for the Auth0 service and used: https://www.npmjs.com/package/auth0-lock for most of the critical parts which is what I was interested in.
Perhaps this would have been good, but most of the video was focused on the back-end service side of managing users / secrets / database logic and again my goal is to use federated login through a third-party like Facebook, Microsoft, Twitter, etc and use implicit grant flow without any server so a lot of this did not apply
The library seems quite good, but it is not a complete solution. It’s only concerned with restricting route access based on Redux state. It’s good for libraries to be focused, but remember the point of this search was to get a complete working auth solution. Someone would have to take the knowledge from these docs and go search some more to find out how to make this work with their actual authentication. Given my experience with ember-simple-auth I was able to bridge the gap.
Given how large the react community is I have to say I was unimpressed. In my opinion authentication is a standard requirement for apps and also not something you want to write yourself. It would definitely ease the barrier to entry if there was more established pattern for this fundamental part of applications where a bunch of experts within the community can share ideas and ensure it’s done correctly and help increase brand quality of React. When you write a custom component it might have slow performance or not look appealing, but when you write custom user authentication you might be leaking tokens or personal information which is not something you get to recover from or fix later. I’m sure the library I’ve written has holes in it which is why I’m hesitant to convert it into a stand-alone package, but hopefully it will at least demonstrate the ideas and serve the basic requirements. Maybe some expert will take it further or maybe someone will enlighten me to an existing solution that does all of this already…
React-Simple-Auth: How does it work?
First let’s start with the goals: I intended this to behave similarly ember-simple-auth because I’m familiar with those APIs and think the authors did a good job at getting the abstraction levels right. So what does Ember-Simple-Auth + Torii provide and how will we re-create these constructs in React?
These secure different parts of the application based on the status of the user. For example, if the user is unauthenticated they should not see a members only section. Or perhaps a page which would require loading data from protected APIs would not make sense to display since there is no access token to send. In most cases, these mixins will automatically redirect to the route designated as the unauthenticated route, otherwise known as the login page.
These are the specific implementations for each of the unique auth providers such as Microsoft, Facebook, Twitter, GitHub, etc. They all may comply to OAuth 2.0 specification, but each have different
/authorize urls, have different query parameters expected to be sent, and different types of tokens returned. Torii providers, have three main functions:
- buildUrl: Build the /authorize url
- open: Opens the window, waits for it to close, and verifies the authentication via inspecting redirect url
- fetch: Given the existing auth data determine if it is still valid and return it or fetch new data if possible
In this case, I agree with the purpose, but I don’t like implementation in Torii. In attempt to make the programming model declarative where you only configure a few required query parameters, client id, and other such values and they compute things behind the scenes it becomes very difficult to understand what to change to get your desired results. This mostly because the default implementation is hidden in base classes and this adds more complexity than seems necessary. Due to all of this, I found it easier to write completely custom providers to handle latest OpenID connect protocol for Facebook or Microsoft instead trying to extend those included in the torii package. Hopefully I can avoid all of that and just use some basic functions that the provider will implement.
(Side note about some framework philosophy: I find React philosophy of having the minimal API surface area is very refreshing and one I hope to get better at applying in my own work. Having these design constraints of implementing a solution using a restricted set of tools / concepts generally leads to simpler designs.)
Here is the interface the providers must implement:
As you can see it’s up to the provider to define what the session is and means to the application. For most cases you simply need a user id, user name, and access token, but it’s completely arbitrary. This will make sense later when we go into code
Service (Ember-Simple-Auth + Torii):
The service is responsible for all the behind the scenes logic such as:
- Manages opening login window and polls window status
- Abstracts the storage mechanism (localStorage in our case)
(Side note: I think using sessionStorage might be more secure, but localStorage allows the session to persist if the user closes the browser / tab saves them from having to login again. Hopefully someone can clarify the pros/cons here.)
- Provide known redirect page which will communicate back to parent page through the shared storage and known storage key
- Manage session lifecycle in storage and pass session to the provider.
(Remember the service doesn’t know what the session is but the provider does. However the service knows how to access the session from storage but the provider doesn’t. These two work together but keep responsibilities isolated)
Here is the interface for the react-simple-auth service:
Understanding the flow:
Now that we’ve explained the concepts the next step to understand how these different pieces work together. I only mentioned the authentication specific pieces, but remember I’m also using react + redux and dealing with state / store is also meaningful to show.
I think this is best explained with a sequence diagram. First we’ll show how you would integrate React Simple Auth into your app. This assumes you already have a provider configured and understand the fundamentals of react + redux and dispatching actions.
Notice there is a single call:
await authService.acquireTokenAsync(provider) that returns the
session object! I can’t overstate how amazing this is. Once you have the session you then simply use redux as you would normally and invoke the action dispatcher to login. The reducer updates the global state and then this allows the
connectedRouterRedirect to re-evaluate and now the
authenticatedSelector is true and the user is redirected to the authenticated part of the application.
The whole login process happens on a single awaitable call.
This is extremely nice for SPA based applications because the alternative is a full redirect to the auth page where the user leaves your app and is then redirected back. This means your app is loaded twice, but once with special urls which mean you likely have logic within the router or app initialization to parse the URL hash. It’s very ugly process.
I hope this seems simple from the surface. Behind the scenes this is actually what’s going on:
Sorry for the low-resolution diagram. I didn’t pay for the premium tier :( If it’s too blurry you can always look at the source code.
Yes, it has way more lines, but hopefully it is not overwhelming. Let’s step through it together. When you call
acquireTokenAsync the service asks the provider to build the authorize url, then opens a window at that url and named using a unique key. The user enters credentials, and the OAuth flow redirects back to our custom redirect.html. The redirect.html simply takes the current window.location which should have the access tokens in the hash and saves it in localStroage at a key which was set as the window.name and is known by parent window. All of these ideas about session management and polling the window are from me looking at how Torii works so please give all the credit to them. I merely re-wrote it in a more compact manner which isn’t specific to Ember apps and am explaining it here for everyone to see. Also to be fair, their libraries are much more robust and supports many more options such as using an embedded iframe and controlling the window size / position etc which are not exposed in the simpler service I wrote.
At this point the user has successfully logged in and can navigate around the restricted parts of our application; however, there are more scenarios we need to cover to be a full auth solution. Read on to find out how we solve those.
Scenario 1: Restoring Session
Any time the application is reloaded we would like to re-use existing sessions if they are still valid / unexpired. This can happen on refreshing page, closing / opening browser, etc.
Again when the initial state of the User Reducer is being setup on application start it first asks the auth service to restore the session. The service attempts to load the session from storage and asks the provider to validate the session. If invalid, the storage is cleared and the call returns undefined, if successful it will return the session object.
Note: A possible area of improvement here is to allow this
restoreSession call to be asynchronous. Currently this has to be synchronous because it runs within the reducer and the reducer is synchronous, but if I could find a way to make it asynchronous it would allow the ability to request a new token if the existing one is expired without throwing away all the data and requiring the user to login. Given I am using Microsoft implicit login flow which does not allow/issue refresh tokens there isn’t a way to acquire new tokens that I know of so making this request async still would not help much. However, I don’t believe all providers have this restriction and it would be nice feature to support.
Scenario 2: Make async request using access token
The whole point of having the user login was so we could make authenticated requests to our service. Let’s review that flow:
Here the service asks the provider to get an access token for a specific resource. With the token then you continue to the normal flow of using redux-thunk to make asynchronous request and once the promise is resolved, dispatch the action such as FETCH_DATA_FULLFILLED which will set the state.
Note: If you noticed the extra resourceId parameter and wondered what it was for. This is a Microsoft provider specific piece of data that has leaked into the implementation of the service. You can safely ignore if you don’t need it. Even in my sample code, my provider simply ignores this and returns the same access token since I’m not familiar how to actually use this with the v2 API. Maybe I will find a way to make this better or remove it all together, but if you read on below I explain what it was intended for. Skip the next section if you aren’t using Microsoft stuff.
I believe Microsoft pioneered the concept of MRRT (Multi-Resource Refresh Token) and this idea has been extended in to their application registration / token issuing process. They allow you to register a single application which declares it requires access to multiple services. This is great because your single app can ask AAD to issue tokens to a bunch of different services like GraphAPI, OneDrive, or Azure, etc while still using proper user delegation and not having to wrap this logic inside your own service. However, all these independent services would require different tokens with specific
aud audience claims. When you login and acquire an access token how does AAD know which service to issue the token for? This is where resourceId comes into play. You say I want an access token for this particular resource such as Office 365. Most providers like Facebook or Twitter would only ever issue a single access token type so I would admit this is flaw in the design that this extra parameter resourceId being exposed on the
service even though most providers won’t use it is something to improve.
Scenario 3: Sign out
I believe it is always best to give your users the freedom to explicitly sign out. This means they don’t want to wait for that token to expire. They want to invalidate them immediately. Perhaps they are at a public computer and want to ensure the next person can’t come by and possibly go visit the same websites they did and re-use the same session. The STS (secure token services) such as AAD usually offer an explicit sign out url and this will look at all the active tokens and revoke them meaning they can no longer be used.
Notice here we first dispatch the action to logout which resets the state back to unauthenticated state and destroys the current session in storage; however, we go even further and redirect the entire window to the sign out url.
I think it might be OK to avoid the page redirect in some application cases, because this will invalidate tokens for all applications not just the current one they’re using and this might not be what your users prefer; however, I did it mostly for learning.
Note: The sign out page takes a redirect url and is suppose to redirect the user back to your application, but for some reason this isn’t working for my application and it kind of leaves the user in awkward AAD landing page asking them to close their browser which is a bit of an eye sore. If any one knows the issue here, please let me know. I was wondering if maybe that sign out page only fully redirects for other types of auth flows using cookies or something.
Here I will debug the sample app and step through all the different steps above to help make it more clear.
I hope you enjoyed learning an approach for adding authentication to your React application. I think it was a good experiment for me to explore the internals of ember-simple-auth and apply these ideas in the world of react. Now that all these frameworks have been around for long enough I see them starting to converge on certain patterns and it’s a good time to bring the best of all of them together. Go add auth to your app and let me know how it goes. Or let me know if you liked the article and think the service is worthy enough to be made into an npm package.
I ended up making it an npm package. As with any packages on npm, *use at your own risk*