Module introduction
- We want allow only logged in users to modify recipes
How Authentication works in SPAs
- In SPAs authentication is managed by tokens
More about jWT
- JSON Web tokens: https://jwt.io/
- https://jwt.io/introduction/
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