If you want to add authentication to your Ionic app, chances are high today that you want to send some specific headers with all of your requests. And although you could use another package inside your app, you can also easily add your own HTTP interceptor for this!
In this Quick Win we will add a custom HTTP interceptor which will intercept all outgoing HTTP calls that our Ionic app makes. Inside those requests we will use a token that we store and retrieve from the Ionic Storage and also catch any errors that happens at the top most level inside the app.
The result looks like the gif above if you inspect your HTTP calls and headers, so let’s dive into it!
Starting our HTTP Interceptor Ionic App
We don’t need any special packages for this but just a blank Ionic app and an additional provider, so go ahead and run:
1 2 3 |
ionic start interceptMe blank cd ionic start interceptMe blank ionic g provider interceptor |
After this your provider will be automatically added to your module, but we need to change it a bit. We want to provide it for the HTTP_INTERCEPTORS
so it looks just like the custom Ionic Error handler. Also, we add the HttpClientModule
and IonicStorageModule
to prepare everything.
Go ahead and change yourapp/app.module.ts to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { InterceptorProvider } from '../providers/interceptor/interceptor'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [ MyApp, HomePage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), IonicStorageModule.forRoot(), HttpClientModule ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage ], providers: [ StatusBar, SplashScreen, { provide: ErrorHandler, useClass: IonicErrorHandler }, { provide: HTTP_INTERCEPTORS, useClass: InterceptorProvider, multi: true }, ] }) export class AppModule {} |
That’s it for the setup, now the Interceptor will be used whenever an HTTP call takes place and we just have to take care of adding the right headers on the fly!
The HTTP Interceptor Provider
The heart of this Quick Win is the actual Http interceptor which only needs to implement the intercept()
function.
Inside the function we have to go through a few steps one by one and especially take care of what we return so don’t confuse Promise and Observables in here and don’t mess up with the async chain! You can find more on this also in the video linked below.
For now, the steps are:
- Get the stored token from Ionic Storage
- Use the token inside
addToken()
- Change the headers of the request if the token exists inside
addToken()
, otherwise return the request again - Handle the cloned request like a regular HTTP call
- Catch errors that might come up and show an alert
Especially the last point is optional, I just wanted to show how you could plug in some error mechanism at that place.
Now open your providers/interceptor/interceptor.ts and change it to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import { AlertController } from 'ionic-angular'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage'; import { Observable } from 'rxjs'; import { _throw } from 'rxjs/observable/throw'; import { catchError, mergeMap } from 'rxjs/operators'; @Injectable() export class InterceptorProvider implements HttpInterceptor { constructor(private storage: Storage, private alertCtrl: AlertController) { } // Intercepts all HTTP requests! intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let promise = this.storage.get('my_token'); return Observable.fromPromise(promise) .mergeMap(token => { let clonedReq = this.addToken(request, token); return next.handle(clonedReq).pipe( catchError(error => { // Perhaps display an error for specific status codes here already? let msg = error.message; let alert = this.alertCtrl.create({ title: error.name, message: msg, buttons: ['OK'] }); alert.present(); // Pass the error to the caller of the function return _throw(error); }) ); }); } // Adds the token to your headers if it exists private addToken(request: HttpRequest<any>, token: any) { if (token) { let clone: HttpRequest<any>; clone = request.clone({ setHeaders: { Accept: `application/json`, 'Content-Type': `application/json`, Authorization: `Bearer ${token}` } }); return clone; } return request; } } |
Now we only need to add a simple test and we are done.
Making HTTP Requests
We should test regular requests and authenticated requests. We don#t really have any authentication in place here, but it would work more or less like the flow inside our app. Plus you would store some information from the token or user so you don’t have to retrieve it all the time.
Anyway, we just switch our auth state around with a function and either write to the storage or remove the key from the storage. That’s all we need to do as the interceptor will handle the rest!
To test successful or failed requests we can also add 2 more functions, but nothing of ths should be new to you so open your pages/home/home.ts and change it to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import { HttpClient } from '@angular/common/http'; import { Storage } from '@ionic/storage'; import { Component } from '@angular/core'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { authenticated = false; message = ''; constructor(private http: HttpClient, private storage: Storage) { } setAuthState(authenticated) { if(authenticated) { this.storage.set('my_token', 'myspecialheadertoken').then(() => { this.authenticated = true; }); } else { this.storage.remove('my_token').then(() => { this.authenticated = false; }); } } getSuccessful() { this.http.get('https://pokeapi.co/api/v2/pokemon/').subscribe(res => { this.message = res['results'][0].name; }); } getFail() { this.http.get('https://notvalid.xy').subscribe( res => {} ,err => { this.message = err.message; } ); } } |
The final piece is to wire up some buttons, but the following code doesn’t really needs any explanation at all besides that it goes into your pages/home/home.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<ion-header> <ion-navbar color="primary"> <ion-title> Ionic HTTP Intercept </ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-card text-center> <ion-card-header> My Auth State: {{ authenticated }} </ion-card-header> <ion-card-content> {{ message }} </ion-card-content> </ion-card> <button ion-button full (click)="setAuthState(true)" *ngIf="!authenticated">Login</button> <button ion-button full color="danger" (click)="setAuthState(false)" *ngIf="authenticated">Logout</button> <button ion-button full color="secondary" (click)="getSuccessful()">Get URL successful</button> <button ion-button full color="light" (click)="getFail()">Get URL with error</button> </ion-content> |
Now you can make your authorised requests which will get the header token added and the regular requests will not plus in case of an error you directly see the alert that something went wrong with your request!
It’s actually not that hard to build your own logic for adding those kind of things, but in a real environment you might need to make the access/refresh token dance if a request fails or in the beginning of your app check if a stored token is still valid.
You can also find a video version of this Quick Win below.