It’s time for Ionic v4 content, and what fit better than the all-time classic login flow example? With Ionic 4 it becomes a lot easier to protect our apps the real Angular way using the Angular Router.
Inside this (quite long) tutorial we will build a dummy authentication flow logic for an Ionic 4 app using Angular. We are not going to use a real backend or users, but you can easily plug in the calls to your API in the right places and use this logic as a blueprint for your authentication.
At the time writing this I’m using the v4 Beta so things might change a bit over time. If you encounter problems, just leave a comment below this tutorial.
We will use the CLI, Angular Router, Guards, Services.. Let’s do this!
Creating the Ionic Basic App
We start with a blank Ionic app and generate a few pages and services at the appropriate places inside our app. Also, I’m using the Angular CLI to generate an additional module inside the members folder (which should be created after running the commands).
Perhaps this will also work with the Ionic CLI soon, but as the Angular CLI is closely integrated in Ionic 4 projects there’s no problem in using it so go ahead and bootstrap your project like this:
ionic start devdacticLogin blank --type=angular cd devdacticLogin npm install --save @ionic/storage ionic g page public/login ionic g page public/register ionic g page members/dashboard ionic g service services/authentication ionic g service services/authGuard ng generate module members/member-routing --flat
The --flat
simple means to create no additional folder for the file!
You can now also go ahead and remove the app/home folder and we will also remove the other references to the HomePage later.
We also want to use the Ionic Storage inside our app which you would later use to store your JWT so we need to add it to our app/app.module.ts like this:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [AppComponent], entryComponents: [], imports: [ BrowserModule, IonicModule.forRoot(), AppRoutingModule, IonicStorageModule.forRoot() ], providers: [ StatusBar, SplashScreen, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } ], bootstrap: [AppComponent] }) export class AppModule {}
That’s the basic setup so far, now we can focus on adding some security.
Adding Authentication Provider and Guard
Like always it’s a good idea to have the authentication logic in one place, and here it is our AuthenticationService
. This service will handle the login/logout and in our case perform just dummy operations.
Normally you would send the credentials to your server and then get something like a token back. In our case we skip that part and directly store a token inside the Ionic Storage. Then we use our BehaviorSubject
to tell everyone that the user is now authenticated.
Other pages can subscribe to this authenticationState
or check if the user is authenticated using the current value of the Subject.
Although we don’t have any real Server attached, the logic and flow is nearly the same so you could use this as a base for your own authentication class.
Now go ahead and change the app/services/authentication.service.ts to:
import { Platform } from '@ionic/angular'; import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage'; import { BehaviorSubject } from 'rxjs'; const TOKEN_KEY = 'auth-token'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { authenticationState = new BehaviorSubject(false); constructor(private storage: Storage, private plt: Platform) { this.plt.ready().then(() => { this.checkToken(); }); } checkToken() { this.storage.get(TOKEN_KEY).then(res => { if (res) { this.authenticationState.next(true); } }) } login() { return this.storage.set(TOKEN_KEY, 'Bearer 1234567').then(() => { this.authenticationState.next(true); }); } logout() { return this.storage.remove(TOKEN_KEY).then(() => { this.authenticationState.next(false); }); } isAuthenticated() { return this.authenticationState.value; } }
Also, we have added a check to the constructor so we look for a stored token once the app starts. By doing this, we can automatically change the authentication state if the user was previously logged in. In a real scenario you could add an expired check here.
To protect our pages we will later use the Angular Router and a check to see if a user is allowed to access a route. With Angular we use the Auth Guards and therefore create our own Guard where we check our service for the current state of the user.
The service implements only this one function in which we use the previous service so go ahead and change the app/services/auth-guard.service.ts to:
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { AuthenticationService } from './authentication.service'; @Injectable({ providedIn: 'root' }) export class AuthGuardService implements CanActivate { constructor(public auth: AuthenticationService) {} canActivate(): boolean { return this.auth.isAuthenticated(); } }
Using these guards is the easiest way to protect pages or URLs of your app so users can’t access them without the proper conditions!
App Routing Logic
With Ionic 4 we can now use the standard Angular routing and therefore we create a routing configuration for our app now. The top routing allows to navigate to the register and login page without any checks, but behind the members path every pages will go through the canActivate
check so they can only be access once a user is authenticated!
You could also add this checks to every single route, but having all of the routes you want to protect within one child routing module helps to save some code and structure your app better.
First of all change the already created app/app-routing.module.ts to:
import { AuthGuardService } from './services/auth-guard.service'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', redirectTo: 'login', pathMatch: 'full' }, { path: 'login', loadChildren: './public/login/login.module#LoginPageModule' }, { path: 'register', loadChildren: './public/register/register.module#RegisterPageModule' }, { path: 'members', canActivate: [AuthGuardService], loadChildren: './members/member-routing.module#MemberRoutingModule' }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
All the pages use lazy loading therefore we are using the files plus a hash and the name of the module.
For the last route with the auth guard we are also not loading the real module but another routing, the routing for the members area. We’ve created this additional file with the Angular CLI and inside we only need to reference the Dashboard, so go ahead and change the app/members/member-routing.module.ts to:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardPageModule' } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class MemberRoutingModule { }
Notice that this is a child routing and therefore the routes are added to the router with forChild()
!
Finally, we can influence our routing logic at the top of the app like you might have seen in v3 in most Firebase tutorials.
We simply subscribe to our own authentication state and if a user becomes authenticated, we automatically switch to the inside area or otherwise for a logged out user back to the login.
To change the according pages we use the Angular Router and navigate to the appropriate pages. Add the code to your app/app.component.ts like this:
import { Router } from '@angular/router'; import { AuthenticationService } from './services/authentication.service'; import { Component } from '@angular/core'; import { Platform } from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; @Component({ selector: 'app-root', templateUrl: 'app.component.html' }) export class AppComponent { constructor( private platform: Platform, private splashScreen: SplashScreen, private statusBar: StatusBar, private authenticationService: AuthenticationService, private router: Router ) { this.initializeApp(); } initializeApp() { this.platform.ready().then(() => { this.statusBar.styleDefault(); this.splashScreen.hide(); this.authenticationService.authenticationState.subscribe(state => { if (state) { this.router.navigate(['members', 'dashboard']); } else { this.router.navigate(['login']); } }); }); } }
Now the app will automatically change pages on login or logout, and we don’t need to navigate from the single pages as all logic is already available at this place.
Public App Pages
We have all logic in place but we still need a few buttons and functions to navigate around our Ionic app. First of all we start with the login and two buttons.
The first will call a function while the second will directly navigate to the register page using a href
. Yes, there are many ways to navigate around!
You can put the code right to your app/public/login/login.page.html:
<ion-header> <ion-toolbar> <ion-title>Login</ion-title> </ion-toolbar> </ion-header> <ion-content padding> <ion-button (click)="login()" expand="block">Login</ion-button> <ion-button expand="block" color="secondary" href="/register" routerDirection="forward">Register</ion-button> </ion-content>
The login is no magic so add our service and call the function once the user hits login. Again, normally you would have the input fields and pass the value to your service but we don’t need that logic right now. Therefore, just add the little changes to your app/public/login/login.page.ts:
import { AuthenticationService } from './../../services/authentication.service'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-login', templateUrl: './login.page.html', styleUrls: ['./login.page.scss'], }) export class LoginPage implements OnInit { constructor(private authService: AuthenticationService) { } ngOnInit() { } login() { this.authService.login(); } }
The register page is more or less pushed like with v3 but we don’t get the back button automatically so we need to add it to our page. Also, you can specify a defaultHref
so whenever someone directly visits the register page the back button would appear and allow navigating back to the login!
To get the button, simple add it like this to your app/public/register/register.page.html:
<ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-back-button defaultHref="/login"></ion-back-button> </ion-buttons> <ion-title>Register</ion-title> </ion-toolbar> </ion-header> <ion-content padding> </ion-content>
That’s all for the public pages, let’s finish with the inside area!
Private Member App Pages
Just like we did with the login before we can now add a button to the dashboard to log a user out again and there’s not much to do right now.
Open your app/members/dashboard/dashboard.page.html and add the button like this:
<ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-button (click)="logout()"> <ion-icon slot="icon-only" name="log-out"></ion-icon> </ion-button> </ion-buttons> <ion-title>My Dashboard</ion-title> </ion-toolbar> </ion-header> <ion-content padding> </ion-content>
Finally, again, just one more line of code to call our service and the rest of the logic will be handled through the Subject in the background, therefore finish this tutorial by changing your app/members/dashboard/dashboard.page.ts:
import { AuthenticationService } from './../../services/authentication.service'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-dashboard', templateUrl: './dashboard.page.html', styleUrls: ['./dashboard.page.scss'], }) export class DashboardPage implements OnInit { constructor(private authService: AuthenticationService) { } ngOnInit() { } logout() { this.authService.logout(); } }
And that’s how you build a simple login flow with Ionic 4 and the Angular router!
Conclusion
With the closer integration of Angular inside Ionic 4 we can now use the full power of the CLI or great features like Guards, routing and more things we’ll talk about soon.
This basic Ionic login example is of course not a replacement or real authentication example, so if you want to see it in action with a JWT server and real tokens just let me know below!
You can also find a video version of this article below.
The post Building a Basic Ionic 4 Login Flow with Angular Router appeared first on Devdactic.