Angular authentication revisited
Most of the applications we build require some kind of authentication. In this tutorial I’ll show you how to build a simple application that uses routing and authentication. We will build a service that handles HTTP calls and stores JWT authentication tokens on the client to restrict access to pages and attach the token to authenticated calls.
It will only cover the frontend concepts built with the new router, we assume a backend exists. If you are not familiar with JWT tokens I would suggest reading this introduction first.
Our application will consist of 3 components associated with a route. The first one is the public homepage with static content. We need another component for logging in with given credentials. And last a component that is only available to those who already logged in.
Here is our AppComponent which will be passed to the main module. To make routing work, we need to place the router-outlet tag in the template. This will be the place where Angular renders the current component’s output.
To setup routing we need to define the routes separately and pass them to the module.
One last bit is to bootstrap our application and pass the providers, declarations and imported modules.
Our application works fine, but everyone can access every page. We need to get an authentication token to restrict access. This logic can be put into a service and become available to every part of the application through dependency injection. If you don’t know how dependency injection works in Angular there is a good article about it in the official documentation.
Our UserService consists of 3 main methods. The first is the login to authenticate with an email address and a password. We will use it in the login component and based on it’s result redirect to the home page and store the received token from the server. The isLoggedIn method will be important when we restrict access to the profile page, showing the current authentication state.
The UserService needs the @Injectable decorator to access the HttpClient service and with it send the login credentials (email, password) to the server (/login). By default the content type is application/json if we pass an object as the body. We can alter the default behaviour by setting it with the HttpHeaders class to plain/text.
Listening to the response of a HTTP call is a bit different from Angular 1. We get an RxJS observable object instead of a promise. Just as with promises we can listen to it’s result, the subscribe method will take the place of the promise’s then method.
We won’t simply pass the raw response to the components, we will transform it to a boolean value and while doing it, check it’s result. The backend service generates a unique token for us, what we can use for authentication. If the backend process is successful, we store the authentication token in LocalStorage and save the state in the service to the loggedIn property.
In our LoginComponent we listen to the result of the login and after a successful login, we redirect the user to the home page. At redirect we reference the route by it’s path declared before.
Now that we are able to log in, it is time to restrict access to the profile page only to logged in users. To make it work we will improve our routes.
For each route definition we can restrict access by creating a guard and adding it to the canActivate property.
The new guard uses the previously defined isLoggedIn method from the UserService to determine whether the user is allowed to see the page or not. It needs to implement the CanActivate interface to become usable by the route definition. In this example we are returning a simple boolean value, but it can be also a Promise that resolves to a boolean value.
Now we have to add the guard to the route definition.
Guards can be added as an array. Because of this you can add multiple guards, which will be executed in a sequence and only let the user see the page if all of them returns true.
The guard is also an Injectable class, so we need to add it to our bootstrap.
The non public pages are now restricted on the client with one of the solutions. The only thing that remains is to send authenticated requests to the server.
We are doing nearly the same we did with the UserService. Add the @Injectable decorator, pass in the HttpClient and call the endpoint. The difference is that we add our authentication token we stored before in the UserService and send it in the Authorization header. With it the backend can check our identity, authenticate us and provide the content we asked for. Otherwise we would get a 401 Unauthorized error message.
Setting the header on every endpoint can be time consuming. We can automate the setting of the Authorization header with the @auth0/angular-jwt package. All requests that are within our whitelist, will be sent with the right headers.
After setting the getter for the JWT token it takes care of the heavy duty.
This package only works with the new HttpClientModule. For the HttpModule angular2-jwt is the recommended package. It does the same, but with the old module.
We authenticated our users, restricted access to pages and sent authenticated requests to the server back. Every time we want to restrict access, we just create a class that implements the CanActivate interface and pass it to the restricted routes.