Login screens like our Angular view above are familiar to all of us. But how do you build one? Is it hard?
It’s not AS HARD as you think. You just need to understand a few fundamental things about tokens, roles, and the browser. Then just set up a few structures to handle the token and roles throughout your application.
But how does a browser store anything? Especially some kind of credential for being logged in?
JWT Tokens
Traditionally cookies and session IDs were used for authentication. A successful login on the API side would create a new row in a database with an ID and user information. The API’s response would return a cookie (session cookie) with the value being the database row’s ID.
On subsequent API calls the session cookie is sent, its ID is looked up in the database, and if the row is found the API allows the request and knows what user made it.
As the industry has trended away from MVC to Frontend JavaScript frameworks and RESTful APIs, authentication technology has advanced as well. The new standard is JWTs (JSON Web Tokens). The concept is similar. Something is sent back and forth between the Frontend and API, which determines if the user is logged in and their identity. The difference for the Frontend is that instead of the browser handling the sending of a session cookie that contains a string (the database ID), it instead sends a string (the JWT) in a header called Authorization.
The advantage of JWTs comes into play on the API side. The JWT provides a stateless session, no database is required. Instead of creating a database row on a successful login, the API encrypts the user information it would have otherwise stored in a database, and this value becomes the string passed back and forth as the JWT. When the Frontend sends this JWT in the Authorization header on subsequent request, the API attempts to decrypt it. If successful, the API allows the request and knows what user made it.
This is more efficient and removes the necessity of a session database and any caching layers. It also conforms well with Microservice design. Any API in the collection can decrypt the token itself, it doesn’t need to ask an Authentication/User service to do any lookups or provide any additional information.
Local Storage
With modern browsers we can store those returned Tokens, along with a user role (something that identifies the user’s permissions in the application), in what is called ‘Local Storage’. We could write this information directly into the browser’s store, however there are good reasons to have a State Machine handle our local storage.
Even without a state machine certain frameworks built on JavaScript can do a good job of keeping aspects of an application from being exposed without merit. But you pair this ability with a JWT Token, user roles, and local storage you can conditionally determine if a user should have access to your UI and if you want to access sensitive information in the cloud. Even if someone hacks into your interface if you have a token based API protecting your sensitive information, malicious users can’t get anything you don’t want them to because it requires a reach-out to the API, and if you’re not on the list, the API rejects you and you get a blank UI. Pretty awesome!
Many clients want these shields in place on their applications. And we have given them this on many projects. Most recently we’ve been using Angular, which has a Router component that can utilize service objects to keep users from accessing any routes you don’t want them too. In Angular we often refer to this Service as an AuthGuard, which checks against a condition to determine if a user should be able to navigate to a particular area of the app.
Together a state machine and high level services give an application a great deal of flexibility, and in our cause a great deal of security.
Sample Angular Architecture
Understanding all that you can work out an architecture that provides all this to your application.
Above, our UI from earlier is generated by the Access Gateway class/component. The Access Gateway handles our login information and provides it to a service object called the Authentication Service. The gateway class is subscribed (using an Observable) to the State Machine’s state object. That subscription will set off a callback method.
The Authentication Service relies on the Http service in order to reach out to our API. The API will authenticate the username and password. If the API recognizes the credentials a JWT token and user role is returned to the service. The service then dispatches the token and role to the State Machine through a set of Reducers.
Reducers are a dictionary of methods which alter our applications state. They are very simple, looking like:
Object.assign(state,{ token:action.payload });
In the above script our state machine is saving an object to our local storage. The method Object.assign is an ES6 method for manipulating an object. Reducers can be robust or straight forward, When developing a state machine we try to make legibility a priority. Knowing that code will be passed around, it’s best to make it easier on the next person. We tend to have very flat and unimpressive reducers.
After Reducers have altered our applications state, the Access Gateway gets informed through the aforementioned subscription callback. Our callback will ask the router to navigate to an app’s internal interface, in this case a simple landing page. This is when the AuthGuard service determines where a user should be able to go, for instance a landing page, based on the stored Role and Token. Since Authguard is a service you can provide it to Angular in any route configuration.
{ path:<path>,
component:HomeComponent,
canActivate:[ AuthGuard ]}
Now all of that is rather complicated when we explain it, but let’s clarify it below using a UML sequence Diagram:
With all this knowledge, you can be confident in developing a safe and secure Access Gateway for any project, whether it be Angular or not. You can use React, Vue, Jade, or another framework and the above design principles will still hold true.
Good luck! Happy Developing!