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
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