Angular2 – Authentication and Route protection

Module introduction

  • We want allow only logged in users to modify recipes

How Authentication works in SPAs

  • In SPAs authentication is managed by tokens

Captura de pantalla 2017-05-23 a las 19.14.33

More about jWT

Creating a signup page and route

  • We create a Sign up form page and his route “/signup”. All associated to a auth/signup.component

Setting up the firebase SDK

  • In firebase console we need to enable basic authentication in the “Authentication” menu option.
  • We need to install firebase npm package
sudo npm install firebase --save

 NOTE: In a normal backend you can have a available endpoint to retrieve a valid token but firebase hasn’t.

  • Create an auth/auth.service.ts with a signupUser method
  • Initialize firebase credentials in a app level: app.component.ts
...
import * as firebase from 'firebase';
...
export class AppComponent implements OnInit{
...
  ngOnInit() {
    firebase.initializeApp({
      apiKey: "AIzaSyCqHQQ7gtNxkERmJzTBDfkn_Sq_2LeY9qc",
      authDomain: "ng-recipe-book-5333b.firebaseapp.com"
    });
  }

 

Signing Users Up

auth.service.ts

import { Injectable } from '@angular/core';
import * as firebase from 'firebase';


@Injectable()
export class AuthService {

  signupUser(mail: string, password:string) {

    firebase.auth().createUserWithEmailAndPassword(mail, password)
    .catch(
      error => console.log(error)
    );

  }
}

signup.component.ts

  onSignup(form: NgForm) {
    const mail = form.value.email;
    const password = form.value.password;

    this.authService.signupUser(mail, password);

  }

Signing Users In

  • We create /signin route and Signin component calling signinUser method on auth service

auth.service.ts

  signinUser(mail: string, password:string) {

    firebase.auth().signInWithEmailAndPassword(mail, password)
    .then(
      response => console.log(response)
    )
    .catch(
      error => console.log(error)
    );

  }
  • Firebase store i the browser information of logged user. You can check it in console -> Application –> Local Storage
  • There it is the accessToken we will using for make future requests.

Requiring a Token (in the Backend)

  • For requiring authentication in the firebase (our backend) we need to set up database rules in the firebase console:
{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

Sending the token

  • Now for making Fetch Data and Save Data action we need to append accessToken of logged users in the http requests.

auth.service.ts

  token: string;

  getToken() {
    firebase.auth().currentUser.getToken()
    .then(
      (token:string) => {
        this.token = token;
      }
    );
    return this.token;
  }
  signinUser(mail: string, password:string) {

    firebase.auth().signInWithEmailAndPassword(mail, password)
    .then(
      response => {
        firebase.auth().currentUser.getToken()
        .then(
          (token:string) => {
            this.token = token;
          }
        )
      }
    )
    .catch(
      error => console.log(error)
    );

  }

data-storage.service.ts

  • We append token in the url of the requests
  storeRecipes() {
    const token = this.authService.getToken();
      return this.http.put('https://ng-recipe-book-5333b.firebaseio.com/recipes.json?auth='+token,
    this.recipesService.getRecipes());
  }

Checking and using Authentication status

  • We want to show dropdown menu at the top right only for authenticated users

auth.service.ts

  isAuthenticated() {
      return this.token != null;
  }

header.component.ts

  constructor(private dataStorageService: DataStorageService,
    private recipesService: RecipesService,
    private authService: AuthService) { }

header.component.html

      <ul class="nav navbar-nav navbar-right">
        <ng-template [ngIf]="!authService.isAuthenticated()">
          <li><a href="/signin">Sign in</a></li>
          <li><a href="/signup">Sign up</a></li>
        </ng-template>
        <li class="dropdown" appDropdown *ngIf="authService.isAuthenticated()">
          <a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a style="cursor: pointer;" (click)="onSaveData()">Save Data</a></li>
            <li><a style="cursor: pointer;" (click)="onFetchData()">Fetch Data</a></li>
          </ul>
        </li>

Adding a Logout button

auth.service.ts

  logout() {
    firebase.auth().signOut();
    this.token = null;
  }

Route protection and redirection example

  • Protect some routes for only logged in users

auth/auth-guard.service.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot , RouterStateSnapshot} from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.authService.isAuthenticated();
  }

}

app-routing.module.ts

...    
    { path: 'new', component: RecipeEditComponent, canActivate: [AuthGuard]},
    { path: ':id', component: RecipeDetailComponent},
    { path: ':id/edit', component: RecipeEditComponent, canActivate: [AuthGuard]}
...
  • After sign in we redirect to main page:

auth.service.ts

  signinUser(mail: string, password:string) {

    firebase.auth().signInWithEmailAndPassword(mail, password)
    .then(
      response => {
        this.router.navigate(['/']);
        firebase.auth().currentUser.getToken()
        .then(
          (token:string) => {
            this.token = token;
          }
        )
      }
    )
    .catch(
      error => console.log(error)
    );

  }

Wrap up

Possible improvements