Angular Course with building a banking application with Tailwind CSS – Lesson 5: User authentication

Duomly - Jun 24 '20 - - Dev Community

This article was originally published at https://www.blog.duomly.com/angular-course-building-banking-application-tailwind-css-lesson-user-authentication/


Intro to Angular Course - User authentication

For almost one month, we are preparing an Angular course for you, and we are building a fin-tech application. Today I'm going to show you how to implement user authentication and how to use it with guards and resolvers.

Our main goal is to check if the user token is active and compatible with the user id. When it's correct, our API will return us the user data, and the user will be able to see the dashboard. If not, the user will be redirected to the login form.

If you don't have the code to start with me today, you can go back to our previous episodes: 

Angular Course With Building a Banking Application With Tailwind CSS – Lesson 1: Start the Project

Angular Course With Building a Banking Application With Tailwind CSS – Lesson 2: Login Form

Angular Course With Building a Banking Application With Tailwind CSS – Lesson 3: User Account Balance

Angular Course With Building a Banking Application With Tailwind CSS – Lesson 4: User Registration

Or if you prefer to get the code and just start from this step, check out our code editor where you can get each lesson:

Angular Course - Lesson 4 - Code

Also, remember that you can use the same backend, by following my friend's tutorial about Golang.

As always, I have a video for you, where I'm going to code everything form today's lesson. So, if you prefer watching tutorials, then just reading them, join me in the video.

Are you excited and ready to start the 5th lesson of our Angular Course and create user authentication?

Let's start!

1. What is canActivate() and how to use it?

In the beginning, let me remind you what is canActivate() method we are going to update today. 

canActivate() is a method that we can use to decide if the route can be activated or not, based on the method result. If this and other implemented guards will return true, then the route is activated and shown to the user. If any of the routes will return false, then the navigation is canceled.

Now, when we understand the canActivate() method, we can open our code and start making changes. 

I'm going to start by saving userId in the session storage while login or register, in the same way as I do with jwt token. Let's open user.service.ts file, and let's add the following code in the login() and register() function.

if (res.data) {
  this.userSubject.next(res.data);
  sessionStorage.setItem('userId', res.data.ID);
}
Enter fullscreen mode Exit fullscreen mode

Also, inside those two functions remove ('dashboard') and change it to ('').
So, your code in the user.service.ts file should look like this now:

login(Username: string, Password: string): any {
  this.http.post(`${this.url}login`, { Username, Password }, httpOptions).toPromise().then((res: any) => {
    if (res && res.jwt) {
      sessionStorage.setItem('jwt', res.jwt);
      this.errorSubject.next(null);
      if (res.data) {
        this.userSubject.next(res.data);
        sessionStorage.setItem('userId', res.data.ID);
      }
      this.router.navigateByUrl('');
    } else if (res.Message) {
      this.errorSubject.next(res.Message);
    }
  });
}

register(Username: string, Email: string, Password: string) {
  this.http.post(`${this.url}register`, { Username, Email, Password }, httpOptions).toPromise().then((res: any) => {
    if (res && res.jwt) {
      sessionStorage.setItem('jwt', res.jwt);
      this.errorSubject.next(null);
      if (res.data) {
        this.userSubject.next(res.data);
        sessionStorage.setItem('userId', res.data.ID);
      }
      this.router.navigateByUrl('');
    } else if (res.Message) {
      this.errorSubject.next(res.Message);
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

Great, we have all data that we need for our authentication right now, and we can start creating the logic for our canActivate() function.

Let's open the auth-guard.service.ts file, and let's refactor the auth-guard file code like below:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardGuard implements CanActivate {
  url = 'http://localhost:4200/api/';
  constructor(
    private router: Router,
    private http: HttpClient,
  ) { }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | any {
    const userId = sessionStorage.getItem('userId');
    const jwtToken = sessionStorage.getItem('jwt');
    const reqHeader = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + jwtToken,
      })
    };

    if (userId && jwtToken) {
      return this.http.get(`${this.url}user/${userId}`, reqHeader).pipe(
        map(res => {
          if (res['data']['ID'] === Number(userId)) {
            return true;
          } else {
            this.router.navigateByUrl('login');
            return false;
          }
        }),
        catchError((err) => {
          return of(false);
        })
      );
    } else {
      this.router.navigateByUrl('login');
      return false;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Great, now we can move forward and update our routes!

2. Update the routes

When our guard is ready, I'd like to do some changes to the routes. When the guard can check if the user is logged in or now, we can change the empty route component, and now try to display the dashboard string from the start. If you are logged in, the dashboard will be shown. If no, then the user will be redirected to the login form. 

Let's open the app-routing.module.ts file, and let's make the changes.

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  {
    path: '',
    component: DashboardComponent,
    canActivate: [AuthGuardGuard],
  }
];
Enter fullscreen mode Exit fullscreen mode

The second step is ready. Now, let's move forward and create the resolver.

3. What is Angular Route Resolve, and how to use it?

Resolve is a method that can be used in the class as a data provider. It means that we can use resolve() with the router to pass data during the navigation. And this functionality is very useful in our case right now. 

In the services folder, let's create another folder, and let's call it user-resolver. Next, inside the user-resolver folder, let's create the user-resolver.service.ts file.

Before we start creating the code in our UserResolverService, we need to create another function to get user data in our UserService. So let's open user.service.ts file, and at the end of this file, let's add the following function.

getUser() {
  const userId = sessionStorage.getItem('userId');
  const jwtToken = sessionStorage.getItem('jwt');
  const reqHeader = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json',
      Authorization: 'Bearer ' + jwtToken,
    })
  };

  return this.http.get(`${this.url}user/${userId}`, reqHeader);
}
Enter fullscreen mode Exit fullscreen mode

Ok, when it's ready, let's open the user-resolver.service.ts, and let's build our resolver. Inside the UserResolverService we will use resolve() function and inside that function, we will call our getUser() function.

import { Injectable } from '@angular/core';
import {
  Resolve,
  RouterStateSnapshot,
  ActivatedRouteSnapshot
} from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from './../user/user.service';

@Injectable({
  providedIn: 'root',
})
export class UserResolverService implements Resolve<any> {
  constructor(private user: UserService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any | Observable<never> {
    return this.user.getUser();
  }
}
Enter fullscreen mode Exit fullscreen mode

Great! Now, we just need to add our UserResolveService to the route. Let's open app-routing.module.ts, and inside the empty route, let's add the resolve property.

{
  path: '',
  component: DashboardComponent,
  canActivate: [AuthGuardGuard],
  resolve: { user: UserResolverService }
}
Enter fullscreen mode Exit fullscreen mode

Cool, there's just one more thing, and our user authentication will be ready!

4. Get the user data from the route

The last step that we need to do is getting the user data from our route. Let's open the dashboard.component.ts file, and let's change our ngOnInit() method. 

Remember, that it's also necessary to import ActivateRoute from @angular/router.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
  user: any = null;

  constructor(
    private route: ActivatedRoute,
  ) { }

  ngOnInit(): void {
    this.route.data
      .subscribe((data: { user: any }) => {
        this.user = data.user.data;
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

The last thing we will do is remove the small error that appears in the console because user data is a little bit late.

Let's open the dashboard.component.html file, and let's add the *ngIf statement in our because we want to display it only when the user is defined.

<app-account-balance *ngIf="user" [user]="user"></app-account-balance>
Enter fullscreen mode Exit fullscreen mode

And voila, we can test our user authentication. 

5. Testing

Let's start our testing from trying to access an empty route as a non logged in user. Make sure your jwt and userId values are not saved in the session storage.

In the Google Chrome browser, you can do it in Developer Tools / Application. Inside the Storage menu, open the Session Storage and delete those values if they are available for your Angular app.

When you are ready, try to open the application on the empty route. You should be redirected to the login form. 

Now you can try both login and registration and reloading the page to see if you are taken to the login form or your user is displayed correctly!

Conclusion

Congratulations, you've just learned how to create user authentication in Angular 9. You know what you should check to let users into the application without the second login. The most important thing you need to remember is to check if the user id and jwt token are compatible. And check if the returned user id is the same as sending user id to prevent another user from accessing the wrong account.

If you didn't manage to get the code or have some bugs, take a look at our Github.

Angular Course - Lesson 5 - Code

I hope this tutorial will help you to create your own Angular apps. 

Also, if you'd like o practice try to create guards for the other views, that will check if the user is already authenticated. Then prevent them from accessing the login and register routes.

Duomly - Programming Online Courses

Thank you for reading, 
Anna from Duomly

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .