Lesson 15

Offline Access and Reauthentication

Allowing offline access and automatic reauthentication

PRO

Lesson Outline

Offline Access and Reauthentication

At the beginning of this module we set two requirements that we have no yet met:

  • If a user has previously logged in, and they have an unexpired token, they should be automatically logged in when returning to the application
  • Users should have offline access to the data in the application that syncs when online

Although we have not yet implemented this functionality, we already have most of the work done. We already store a users information in local storage when they log in, which includes a token for re-authentication, and we already have all the data we need stored locally in PouchDB, so we don't need an Internet connection to access existing data. As we have already discussed, PouchDB supports modifying data locally and syncing when online, so there's no issue there either.

We just need to make a couple of changes to the application so that the user doesn't get stuck at the login screen when they are offline and if they have already logged in and have a valid token. In order to access the application while offline the user will have to have already logged in previously while online - it's not possible to authenticate whilst offline.

Modify the Auth Service

In order to facilitate this functionality, we are going to add an additional function to our Auth service called reauthenticate. This will attempt to make use of the user data stored in local storage to perform the authentication - if a valid token is available then the authentication will pass without having to check against the server.

Modify src/services/auth.service.ts to import and inject NgZone:

import { Injectable, NgZone } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NavController } from '@ionic/angular';
import { UserService } from './user.service';
import { DataService } from './data.service';
import { SERVER_ADDRESS } from '../../environments/environment';
import { Observable } from 'rxjs';

import { Credentials } from '../interfaces/credentials';
import { RegistrationDetails } from '../interfaces/registration-details';
import { User } from '../interfaces/user';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(
    private http: HttpClient,
    private userService: UserService,
    private dataService: DataService,
    private navCtrl: NavController,
    private zone: NgZone
  ) {}

  //...snip

Modify src/services/auth.service.ts to include the following function:

  async reauthenticate(): Promise<boolean> {
    if (this.dataService.db === null) {
      const userData = await this.userService.getUserData();

      if (userData !== null) {
        const now = new Date();
        const expires = new Date(userData.expires);

        if (expires > now) {
          this.userService.saveUserData(userData);
          this.zone.runOutsideAngular(() => {
            this.dataService.initDatabase(userData.userDBs['hangz-app']);
          });

          return true;
        } else {
          throw new Error('Token expired');
        }
      } else {
        throw new Error('No user data available');
      }
    } else {
      return true;
    }
  }

We are handling a few different cases here, but we are just resolving or rejecting a promise based on whether or not the user is able to be authenticated.

We first check if this.dataService.db is null, if it is not then that means that the user has already been authenticated because the database has already been initialised. We may call this function multiple times in a single session, so this is just skipping the normal process if the user has already successfully authenticated or re-authenticated.

Next, we check if user data is available. If it isn't then we reject the promise by throwing an error. If it is we check if the token is expired, and if it isn't then we initialise the database with the existing data and resolve the promise by returning.

Utilise the Reauthenticate Function

Now that we have our reauthenticate function defined, we just need to make use of it throughout the application. First, we will add it to the login page.

Modify src/app/login/login.page.ts to include the following:

  async ngOnInit() {
    try {
      await this.authService.reauthenticate();
      this.navCtrl.navigateRoot('/home/tabs/notices');
    } catch (err) {
      console.log(err);
    }
  }

In the ngOnInit function we just trigger the reauthenticate method, and if it executes without throwing and error we send the user straight through to the home page.

It might seem like enough to just add the reauthenticate to the login page, but remember when we are running the application through a normal browser we can also load directly to a particular page by using a specific URL. To handle this, we are going to trigger this function in our notices and chat pages too.

NOTE: This will solve our issue where we receive an error when attempting to load anything except the login page.

Modify src/app/notices/notices.page.ts to reflect the following:

PRO

Thanks for checking out the preview of this lesson!

The full version of this lesson is only available to pro members. If you would like full access to this module and all of the other pro modules on Elite Ionic you can become a pro member (or log in if you are already a member).