Authentication & Route Protection in Angular Apps
how authentication works in a SPA
traditional web apps store the session on the server
cookies are passed back and forth
with SPA Angular builds the HTML on the client
no sessions on the server
backend can be a RESTful service
server returns a token to client
client attaches to token to requests for authentication

Top

Index

sending a token
add token as string property of auth.service
in the signinUser method the signInWithEmailAndPassword invocation returns a promise
when async call returns a call to firebase.auth().currentUser.getIdToken is made firebase...getIdToken method returns a promise
when async call returns the token property is set to the token returned by firebase
add getToken method to auth.service
in getToken method call firebase...getIdToken method
the token property is returned synchronously before firebase...getIdToken returns
thia code should be refactored to keep it DRY
    import * as firebase from 'firebase';

    export class AuthService {
        token: string;
        ...
        signinUser(email: string, password: string) {
            firebase.auth().signInWithEmailAndPassword(email, password)
                .then(
                    // response contains the token
                    // firebase automatically stores the token when the signin method in invoked
                    response => {
                        console.log(response);
                        firebase.auth().currentUser.getIdToken()
                            .then(
                                (token: string) => this.token = token
                            );
                    }
                )
                .catch(
                    error => console.log(error)
                );
        }
        getToken() {
            // getToken returns a promise
            firebase.auth().currentUser.getIdToken()
                .then(
                    // when promise is satisfied (async call completed) assign the token
                    (token: string) => this.token = token
                );
            // return the existing token
            return this.token;
        }
    }
        
in data-storage.service import the auth.service
get the token from the auth.service
append the token as the value to the auth key in the query string
    ...
    import { AuthService } from '../auth/auth.service';

    @Injectable()
    export class DataStorageService {
        constructor(
            private httpService: Http,
            private recipeService: RecipeService,
            private authService: AuthService) { }

        storeRecipes() {
            const token = this.authService.getToken();
            return this.httpService.put(
                'https://ng-recipe-book-47065.firebaseio.com/recipes.json?auth=' + token,
                this.recipeService.getRecipes());
        }

        getRecipes() {
            const token = this.authService.getToken();

            this.httpService.get('https://ng-recipe-book-47065.firebaseio.com/recipes.json?auth=' + token)
                .map(...);
        }
    }
        
>

Top

Index

route protection and redirection example
in auth.service inject a Router
use router to naviagte to root page on successful login
    import * as firebase from 'firebase';
    import { Router } from '@angular/router';
    import { Injectable } from '@angular/core';

    @Injectable()
    export class AuthService {
        token: string;
        constructor(private router: Router){}
        ...
        signinUser(email: string, password: string) {
            firebase.auth().signInWithEmailAndPassword(email, password)
                .then(
                    // response contains the token
                    // firebase automatically stores the token when the sighin method in invoked
                    response => {
                        this.router.navigate(['/']);
                        firebase.auth().currentUser.getIdToken()
                            .then(
                                (token: string) => this.token = token
                            );
                        }
                )
                .catch(
                    error => console.log(error)
                );
        }
        ...
    }
        
add auth-guard service in auth folder
inject the auth.service
implement CanActivate interface
    import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
    import { Injectable } from '@angular/core';
    import { AuthService } from './auth.service';

    @Injectable()
    export class AuthGuard implements CanActivate {

        constructor(private authService: AuthService) { }
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
            return this.authService.isAuthenticated();
        }
    }
        
in app-routing.module add the guard to the canActivate array of the 'new' and 'id/edit' paths
    ...
    import { AuthGuard } from './auth/auth-guard.service';

    const appRoutes: Routes = [
        /* full pathMatch indicates redirect only if full path is empty */
        ...
        {
            path: 'recipes', component: RecipesComponent, children: [
                { path: '', component: RecipeStartComponent },
                { path: 'new', component: RecipeEditComponent, canActivate: [AuthGuard] },
                // paths without dynamic IDs must be first
                { path: ':id', component: RecipeDetailComponent },
                { path: ':id/edit', component: RecipeEditComponent , canActivate: [AuthGuard] },
            ]
        },
        ...
    ];
    @NgModule({
        imports: [RouterModule.forRoot(appRoutes)],
        exports: [RouterModule]
    })
    export class AppRoutingModule { }
        
in app module add auth-guard service to the providers

Top

Index

n4jvp.com