Authentication in Angular
IMPORTANT! This tutorial is using the old Angular router (@angular/router-deprecated). A new article is published on how to authenticate with the new router (@angular/router version 3.0+). If you are still using the old router you can read the upgrade guide to the new one.
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 HTTP calls.
The tutorial will only cover the frontend concepts, we will 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 bootstrap function. With @RouteConfig decorator we can tell the application that these routes exist with given urls and components. It is important to give them names, because later we will use them as references redirecting to another page.
To make routing work one more thing is needed, the RouterOutlet directive. This will be the place where Angular renders the current components output based on the url.
Our application works fine, but everyone can access every page. We need to get an authentication token to restrict it. 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 Http service and with it send the login credentials (email, password) to the server (/login). By default the content type is plain/text and we need to set it with the help of Headers to application/json. 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 of our requests. 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. As you can see we reference the route by it’s @RouteConfig name 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 accomplish this we will take a look at our RouterOutlet.
When Angular loads the component of the route the RouterOutlet’s activate function is called with the actual Instruction. The Instruction describes information for the router how to transition to the next component. From it we can extract the current url and based on it redirect the user when trying to access a restricted page without logging in.
When extending the built in RouterOutlet we can extend it’s constructor with one additional parameter, the UserService. It can provide whether the user is logged in and combining it with the list of public urls we can decide which navigations are allowed in the _canActivate method.
When we navigate in our application the next component gets instantiated and the activate method of the RouterOutlet gets called with the current ComponentInstruction. From the instruction we can easily get the current url and based on it decide if we should redirect the user to the login page. If everything is okay we just pass the instruction to the parent method and the application displays the component.
And with this step we secured every non public page in the application, no need to manually add it to every restricted component. Just need to pass our new class instead of the built-in one.
The only thing can be a bit weird of this approach is depending on an array of urls instead of lifecycle events or route configuration.
To see this solution in action check this Github repository.
Angular gives us another way of restricting access to page components and it can be accomplished with the @CanActivate router lifecycle decorator. Before activating the component the function passed to the @CanActivate decorator gets resolved by the router (can return promises also) and if the return value is false, the component won’t get activated.
This way we have to manually decorate the components we want to restrict, but it can be added not only to page level components but subcomponents also.
A serious drawback of this feature for now in version beta.8 is that dependency injection is not available inside this function. We can’t access the UserService or the Router to redirect the user. There is also an ongoing issue for this on Github.
To access the DI we need a bit of hack for this to work. When we bootstrap our application it returns a promise which will be resolved with the application’s DI injector and through this if we cache it, we can access the objects inside it.
After this bootstrap we can import the appInjector inside the isLoggedIn function and redirect, when the user is not logged in as we did when extending the RouterOutlet. A working application is available with this solution in this Plunkr.
Note: The @CanActivate lifecycle hook can also be used with extension, thanks to Tamás Csaba for the tip.
It is important to note that the solutions don’t play well with each other. The reason is when using @CanActive decorator and it resolves false, the activate method of the RouterOutlet won’t be called.
The non public pages are now restricted on the client with one of the solutions. The one 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 HttpService 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.
With some simple steps we authenticated our users, restricted access to pages and sent authenticated requests to the server back. Either way we go for restricting access to pages, both works, but till dependency injection is solved for the @CanActivate decorator I would go with extending the RouterOutlet.