Lesson 14

Login and Registration

Enabling user registration and authentication in the application

PRO

Lesson Outline

Login and Registration

In the last lesson, we set up a simple Node/Express server that uses SuperLogin to facilitate user registration and authentication. In the same way that we can interact directly with CouchDB using an HTTP API (but we use PouchDB API instead), we can also interact with SuperLogin's functionality through HTTP requests.

We are going to walk through implementing this in our application in this lesson, but to give you the basic idea, if we wanted to create a new user we would make a POST request like this:

this.http.post(SERVER_ADDRESS + 'auth/register', details);

or if we wanted to validate that a particular username was valid, we would make a GET request like this:

return this.http.get(SERVER_ADDRESS + 'auth/validate-username/' + username);

In this lesson, we will be creating two new services to help us deal with the integration with SuperLogin, and then we will also be adding two new pages to handle logging in and account creation.

User Service

We are going to create a User service that we will be able to use throughout the application to access the currently logged in user's details. This is going to be a very simple service, all it will do is keep a reference to the currently logged in users data, and save and fetch that data from storage.

As we did for our Chat and Notice types, we will also create an interface for a User.

Create a file at src/app/interfaces/user.ts and add the following:

export interface User {
  issued: number;
  expires: number;
  provider: string;
  ip: string;
  token: string;
  password: string;
  user_id: string;
  roles: string[];
  userDBs: {
    ['hangz-app']: string;
  };
}

NOTE: We are using ["hangz-app"] here because our database name is hyphenated, otherwise we could have just done something like hangz: string

If you are wondering how I came up with these properties for the User object, you can see the structure of the data that is returned by SuperLogin in the documentation. A response from SuperLogin might look like this:

{
  "issued": 1440232999594,
  "expires": 1440319399594,
  "provider": "local",
  "ip": "127.0.0.1",
  "token": "aViSVnaDRFKFfdepdXtiEg",
  "password": "p7l9VCNbTbOVeuvEBhYW_A",
  "user_id": "joesmith",
  "roles": ["user"],
  "userDBs": {
    "supertest": "http://aViSVnaDRFKFfdepdXtiEg:p7l9VCNbTbOVeuvEBhYW_A@localhost:5984/supertest$joesmith"
  }
}

Create the service with the following command:

ionic g service services/User

Modify src/app/services/user.service.ts to reflect the following:

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage-angular';

import { User } from '../interfaces/user';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  public currentUser: User;
  private _storage: Storage;
  private storageInitialised: boolean = false;

  constructor(private storage: Storage) {}

  async init() {
    const storage = await this.storage.create();
    this._storage = storage;
    this.storageInitialised = true;
  }

  async saveUserData(data: User): Promise<void> {
    if (!this.storageInitialised) {
      await this.init();
    }
    this.currentUser = data;
    this._storage.set('hangzUserData', data);
  }

  async getUserData(): Promise<User> {
    if (!this.storageInitialised) {
      await this.init();
    }

    return this._storage.get('hangzUserData');
  }
}

We have a class member called currentUser that we will use to store the currently logged in user's data, and we will be able to access that from anywhere in the application directly through this provider.

We also have a saveUserData method that we will pass the user's data into, this will set the currentUser member and also save the data to local storage. The getUserData method will retrieve any existing user data that has been saved to storage.

Auth Service

Now we are going to create the Auth service that will handle the integration with SuperLogin, this one is a little bit more complex but it's still reasonably straightforward - SuperLogin already does most of the heavy lifting for us.

Create the provider with the following command:

ionic g service services/Auth

We are going to create a couple of new interfaces now as well. We will create an interface to represent the data object containing a user's credentials when they are attempting to log in, and an interface to represent the data required to create a new user account.

Create a file at src/app/interfaces/credentials.ts and add the following:

export interface Credentials {
  username: string;
  password: string;
}

Create a file at src/app/interfaces/registration-details.ts and add the following:

export interface RegistrationDetails {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
}

Modify src/app/services/auth.service.ts to reflect the following:

import { Injectable } 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
  ) {}

  authenticate(credentials: Credentials): Observable<User> {
    return this.http.post<User>(`${SERVER_ADDRESS}/auth/login`, credentials);
  }

  async logout(): Promise<void> {
    const headers = new HttpHeaders();

    headers.append(
      'Authorization',
      `Bearer ${this.userService.currentUser.token}:${this.userService.currentUser.password}`
    );

    this.http
      .post(`${SERVER_ADDRESS}/auth/logout`, {}, { headers: headers })
      .subscribe(() => {});

    try {
      await this.dataService.db.destroy();
      this.dataService.db = null;
      this.userService.saveUserData(null);
      this.navCtrl.navigateRoot('/login');
    } catch (err) {
      console.log(err);
      console.log('could not destroy db');
    }
  }

  register(details: RegistrationDetails): Observable<User> {
    return this.http.post<User>(`${SERVER_ADDRESS}/auth/register`, details);
  }

  validateUsername(username: string): Observable<Object> {
    return this.http.get(
      `${SERVER_ADDRESS}/auth/validate-username/${username}`
    );
  }

  validateEmail(email: string): Observable<Object> {
    const encodedEmail = encodeURIComponent(email);

    return this.http.get(
      `${SERVER_ADDRESS}/auth/validate-email/${encodedEmail}`
    );
  }
}

Although this is not the limit of what SuperLogin can do, this service facilitates all of the integrations we need which are:

  • Logging In
  • Logging out
  • Account Creation
  • Validating usernames
  • Validating emails
PRO

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).