Skip to content
This repository has been archived by the owner on Nov 29, 2023. It is now read-only.

Token based Authentication and Access Control

Victor Dias edited this page Sep 24, 2018 · 6 revisions

If you are not familiar with token-based authentication I recommend this article to easily understand and remember how token-based authentication works.

Authentication

Login Form

Authentication is achieved with an email address and password. This means implementing a login form where users can enter their credentials.

    <form (ngSubmit)="loginUser(user)" [formGroup]="user" novalidate>
      <ion-list no-lines>
        <ion-item>
          <ion-input type="email" placeholder="Email" formControlName="email"></ion-input>
        </ion-item>
        <ion-item>
          <ion-input type="password" [placeholder]="'Password' | translate" formControlName="password"></ion-input>
        </ion-item>
        <button type="submit" [disabled]="user.invalid" ion-button block round outline color="light">Login</button>
      </ion-list>
    </form>

Login Controller

The corresponding controller look like this:

constructor(...) {
    this.user = this.fb.group({
      email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
      password: ['', Validators.compose([Validators.required])]
    });
}

loginUser(user) {    
    this.AuthProvider.signIn(user.value.email, user.value.password)
    .then(response => {
      this.navCtrl.setRoot('HomePage');
    })
    .catch (err => {
      ...
    })
}

Authentication service

The Authentication service act as gateway between controller and api Service.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Auth, AuthError } from '@models/auth.interface';
import { ApiProvider } from '@providers/api';

@Injectable()
export class AuthService {

  constructor(public http: HttpClient, public ApiService: ApiProvider) {
    console.log('Hello AuthProvider Provider');
  }

  signIn(email: string, password: string): Promise<Auth | AuthError> {

    return this.ApiService.login(email, password);
  }

  signOut() {}

  isAuthenticated() {}

}

Api Service

Our API endpoint /vistors/login returns below response when authentication succeed:

{
    "success": true,
    "token": "14cff39e74468e78494ab0778776d23f3aedca53",
    "visitor": {
        "first_name": "John",
        "last_name": "Smith",
        "email": "[email protected]",
        "site": "john.meumobi.com"
    }
}

If credentials are wrong the API respond with code 401 and following response:

{"error":"invalid email or password"}

Authentication data persistence

Introduction

We need a mechanism to store Auth data we receive from the API, namely the user details (name, email) and token (X-Visitor-Token). Because the data will be unique across our entire application, we’ll store it in a service called AuthPersistenceService.

Auth data are saved on device (Ionic Storage) for persistence. As long as that informations are present, the user can be considered logged in. And, when a user needs to make a new call to REST API, that token can be used.

Putting the authentication data persistence logic in a separate service promotes a nice separation of concern between the authentication process and the storage of Auth data. This ensures that we don’t have to change the AuthPersistence Service if the authentication flow changes and allows us to easily mock Auth data in unit tests.

AuthPersistence Service

export class AuthDataPersistenceService {
  isLogged$ = new Subject<boolean>();

  constructor(
    private storage: Storage
  ) {
    this.hasToken()
    .then(data => this.isLogged$.next(data));
  }
  
  /**
  * if we have token the user is loggedIn
  * @returns {boolean}
  */
  private hasToken(): Promise<boolean> {
    return this.storage.get("authData")
    .then(
      data => !!JSON.parse(data)
    )
  }
  
  get(): Promise<Auth> {
    console.log("get authData on ionicStorage");
    return this.storage.get("authData")
    .then(data => JSON.parse(data))
  }

Notice how AuthPersistence Service is not aware of any authentication logic. It’s only responsible for storing Auth data.

then on app.component.ts

  ngOnInit() {
    this.authDataPersistenceService.isLoggedSubject.subscribe( isLogged => {
      console.log(isLogged);
      if (isLogged) {
        this.rootPage = 'HomePage';
      } 
      if (!isLogged){
        this.rootPage = 'LoginPage';
      }
    })
  }

  ngOnDestroy() {
    this.authDataPersistenceService.isLoggedSubject.unsubscribe();
  }

Sending The User’s Token With API Requests

Route Restriction

Protect parts of our application from non authorized access.

Retrieve user profile

If we'd like to manage further properties of user, not related to authentication, as picture, landlineNumber, preferredLanguage, etc. we'll create a UserProfileService with following signature:

- create()
- update()
- fetchByEmail()
- getCurrent()
- setCurrent()

app.component.ts

public authState: Observable<AuthData>;
..

translate.setDefaultLang('en');
this.authState = this.authDataPersistenceService.authState;
const authObserver = this.authState.subscribe((authData: AuthData) => {
      if (authData) {
        userProfileService.fetchByEmail(authData.visitor.email).subscribe((userProfile: UserProfile) => {
            if (userProfile.preferredLanguage){
              translate.use(userProfile.preferredLanguage);          
            }                     
            userService.setCurrent(userProfile);
            this.rootPage = 'HomePage';
          } else {
            this.rootPage = 'LoginPage';
            authObserver.unsubscribe();
          }
        });
      } else {
        this.rootPage = 'LoginPage';  
      }
    });
})