The premise is to show user profile info (name, nickname, email, etc) on /profile route and make a guard for it, so when someone tries to reach it without being authenticated, gets redirected to home route, we’ll also write an Auth Service to wrap all of our auth related stuff in one place.
First and foremost, we need to create an Authetication Service which will wrap all of Auth related states and actions in one place, in particular login/logout actions and user authentication status (user is logged in/out), and all off it asynchronously as a stream (brace yourselves RxJS is incoming).
ng g s services/auth
s = service
This is our authentication service (./src/app/services/auth.service.ts):
Looks intimidating at first glance, but trust me, there is nothing complicated, so let’s break it down:
Line 9: userSignedIn$ is a RxJs Subject (of type boolean), which means it is an Observer and an Observable at the same time, which means we can control its value in our service, and observe its changes outside of it.
Our constructor will initialize the login state by using TokenService’s validateToken() method, which will validate current token (if exists) against the Rails backend, this operation is asynchronous and returns in its body an object that looks like this:
So we can use success key to determine wether the user is logged in with a valid token or not (line 14).
Dollar sign ($) at the end of the name is a convention to specify that this is not a simple variable, but an observable stream of data which changes in time.
Let’s not forget to inject it into our main AppModule’s providers (./src/app/app.module.ts):
...
import {AuthService} from "./services/auth.service";
@NgModule({declarations: [AppComponent,HomeComponent,ToolbarComponent,AuthDialogComponent,LoginFormComponent,RegisterFormComponent,ProfileComponent],imports: [BrowserModule,FormsModule,HttpModule,AppRoutingModule,MaterializeModule,],providers: [ Angular2TokenService, AuthService],bootstrap: [AppComponent]})export class AppModule { }
Now we need to make some small changes to our Login and Toolbar components in order to use AuthService instead of Angular2TokenService directly.
Let’s start with ToolBarComponent class (./src/app/toolbar/toolbar.component.ts):
As you can see, we got rid of Angular2TokenService and injected our custom AuthService instead (line 16), we also created a logOut() method which will call logOutUser action on our AuthService, and using Angular Router, redirect the user to home when logout action completes (line 21).
Now let’s change Toolbar’s template (./src/app/toolbar/toolbar.component.html):
Now we are using our AuthService’s userSignedIn$ Subject to change the state of the toolbar when user logs in or out, considering that userSignedIn$ is not a simple value, but an asynchrnous stream of values chaning in time, we need to use Angular’s async pipe to listen to its changes (lines 11, 12, 14, 15). We’ll also link the click event on logout button to toobar component’s logOut() method we created earlier (line 15).
Changes on login form will be extremely minimal, we just need to change the LoginFormComponent class (./src/app/login-form/login-form.component.ts)
The only thing we needed to do is getting rid of Angular2TokenService and replacing it with our AuthService and its logInUser method.
Don’t forget to apply the same changes to RegisterFormComponent
And that’s it, refactoring is over.
First step is obvious, let’s generate our ProfileComponent that will display current user’s info:
ng g c profile
The component should be automatically imported in AppModule’s declarations.
In our router module (./src/app/app-routing.module.ts), let’s declare the profile route:
Import the profile component:
import {ProfileComponent} from "./profile/profile.component";
Declare the route:
...
const routes: Routes = [{path: '',component: HomeComponent,pathMatch: 'full'},{path: 'home',component: HomeComponent},{path: 'profile',component: ProfileComponent}];...
As for the design of profile page, it’ll be pretty simple, we’ll display user’s email, name, and user name in a Materialize Card and use its footer to add a logout button.
Let’s inject Angular2TokenService into our ProfileComponent, and add a logout method (./app/profile/profile.component.ts):
We’ll be making use of Angular2TokenService to display user’s personal information, AuthService to implment the logout action, and the Router to redirect after logout completes, so we need to inject these in our ProfileComponent (lines 14–16)
Nn line 18, we have a logOut action which redirects the user to home when it completes.
Reimplementing the logOut action for each component seems repetitive, but this is actually useful, because we can implement different post-logout behaviours for each component, for example redirecting to some other route instead of home.
Profile Component’s template, (./src/app/profile/profile.component.html)
Angular2TokenService’s currentUserData attribute provides information about our user if he is logged in (email, name, nickname, etc.), its value is undefined when user is not logged in.
One line 15, we are usign *ngIf structural directive to test if there is any data to display, if not we will not show the user info card.
On line 31 we also have a logout button, linked to logOut method of our ProfileComponent class.
Be sure to add a cleafix class to ./src/app/profile/profile.component.sass because Materialize doesn’t implement it.
.clearfixoverflow: auto
And this is what we get when we navigate to /profile route while being logged in:
User info card
Now the problem is, we still can navigate to /profile route even if we are not logged in:
Angular’s Route Guards can help us to fix this issue.
Route Guards are pretty easy to understand, it’s a simple class which implements CanActivate interface, this interface demands us to implement a canActivate() method**,** which returns a single boolean value (or an Observable of type boolean, although I couldn’t manage to make them work properly), and depending on that boolean value the Router will allow or forbid the activation of a route, the logic is up to us to implement, so let’s do that.
Create an AuthGuard class in (./src/app/guards/auth.guard.ts):
As you can see, our AuthGuard class implements CanActivate interface and its canActivate() method, the logic is simple, Angular2TokenService’s userSignedIn() method returns a signle boolean value, we’ll use it to determine if user is signed in, if so, we allow the activation by returning true, of not, we’ll redirect to home and forbid the activation by returning false.
Don’t forget to inject it in our AppModule’s providers:
...
import {AuthGuard} from "./guards/auth.guard";
@NgModule({declarations: [AppComponent,HomeComponent,ToolbarComponent,AuthDialogComponent,LoginFormComponent,RegisterFormComponent,ProfileComponent],imports: [BrowserModule,FormsModule,HttpModule,AppRoutingModule,MaterializeModule,],providers: [ Angular2TokenService, AuthService, AuthGuard],bootstrap: [AppComponent]})export class AppModule { }
Now let’s protect our /profile route, open ./src/app/app-routing.module.ts and set profile route’s guard:
import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';import {HomeComponent} from "./home/home.component";import {ProfileComponent} from "./profile/profile.component";import {AuthGuard} from "./guards/auth.guard";
const routes: Routes = [{path: '',component: HomeComponent,pathMatch: 'full'},{path: 'home',component: HomeComponent},{path: 'profile',component: ProfileComponent,canActivate: [AuthGuard]}];
@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule],providers: []})
export class AppRoutingModule { }
canActivate key takes an array of guards, in case you have several guards on a single route, all of them must return true in order for the route to be activated.
Now, when we go to /profile without being logged in, we’ll get redirected to home, and any Router Link with /profile as a route, will produce no effect when clicked on.
And there you go, a profile page, protected by a Route Guard.
You can get the final code for this part on GitHub: https://github.com/avatsaev/angular-token-auth-seed/tree/profile
Let me know if you have any remarks about the tutorial, I’ll be happy to help and correct it if something isn’t clear enough :)
Thank you for reading this series, follow me on Twitter (https://twitter.com/avatsaev) for more interesting stuff on Angular 2+ and Rails.