Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently store and use Authentication in Ionic 3

I'm currently developing an Ionic (3.4) app for iOS/Android. I'm not familiar with authentification processes or with applications.

After using HTTP request in a provider, I get my object, the user. But I have absolutely no idea how to store it or to deal with it. What are the good practices:

  • Am I suppose to just store the object in the provider and call in in every single page I have?
  • Am I suppose to store it as a sort of global object in the app?
  • Or am I suppose (as bellow) to transfer it as a Navigation variable from one page to another? (I guess that's not the right answer)

And finally, how will the application react on reloading/restarting: how to avoid people to fill the form again and again?

Here is my service:

@Injectable()
export class UserProvider {
    data = {};
    username: string;
    password: string;

    constructor(private _http: Http) {
        console.log('Hello UserProvider Provider');
    }

    getUser(username:string, password:string){

        let urlSearchParams = new URLSearchParams();
        urlSearchParams.append('username', username);
        urlSearchParams.append('password', password);
        let params = urlSearchParams.toString()

        var headers = new Headers();
        headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

        return this._http
        .post("http://api.minesdedouai.fr/user/login", params, {headers:headers} )
        .map(response => response.json())

    }

}

I'm aware that a login request is supposed to use some token, but as I said, this is a small student's API, we're not pro and it's working.

And this is the method that uses the service, somewhere in a component (a page).

onLogin(form: NgForm) {
  if (form.valid) {
    this._userProvider.getUser(this.login.username, this.login.password)
    .subscribe((response) => {
      this.user = response;
      console.log("Authentification Success");
      this.navCtrl.push(TabsPage, {user: this.user});
    },
    (error) => {
      if (error.status===403){
        console.log("Wrong User")
      }
      else{
        console.log(error);
      }

    });
  }
}

Thank you very much

like image 518
Victor Chapalain Avatar asked Jul 05 '17 21:07

Victor Chapalain


1 Answers

I will expose you how I deal with authentification on my ionic app.

The first step consists of creating a representation of the data you want to store. I used to store some user's information and a jwt token for the authentification.

user.ts :

export class AuthenticatedUser {
  private _username: string;
  private _id: number;
  private _token: string;

  public static GetNewInstance(): AuthenticatedUser {
    return new AuthenticatedUser(null, null, null)
  }

  public static ParseFromObject(object): AuthenticatedUser {
    const model = AuthenticatedUser.GetNewInstance();

    if (object) {
      model.username = object.username;
      model.id = object.id;
      model.token = object.token;
    }

    return model;
  }

  constructor(username: string, id: number, token: string) {
    this._username = username;
    this._id = id;
    this._token = token;
  }

  get username(): string {
    return this._username;
  }

  set username(value: string) {
    this._username = value;
  }

  get id(): number {
    return this._id;
  }

  set id(value: number) {
    this._id = value;
  }

  get token(): string {
    return this._token;
  }

  set token(value: string) {
    this._token = value;
  }
}
  • The GetNewInstance method is used to return an instantiated AuthenticatedUser variable.
  • The ParseFromObject method is used to return an AuthenticatedUser from an object. It's very useful to convert an api response object into a model.

After that I create some services. The first one is the UserService. I use it to access my AuthenticatedUser inside my local storage. In the local storage the data are persistence and will not be delete when you close the app.

user.service.ts :

export class UsersService {

  private _user: Subject<AuthenticatedUser> = new Subject<AuthenticatedUser>();

  constructor(private storage: Storage) {}

  /* ---------------------------------------------------------------------------------------------------------------- */
  /* Observable use object                                                                                            */

  public subscribeToUserService(callback) {
    return this._user.subscribe(callback);
  }

  public updateUserService(user: AuthenticatedUser) {
    this._user.next(user);
  }

  /* ---------------------------------------------------------------------------------------------------------------- */
  /* User storage management                                                                                          */

  /**
   * Write user properties in the local storage.
   *
   * @param user
   * @returns {Promise<AuthenticatedUser>}
   */
  createOnStorage(user: AuthenticatedUser): Promise<AuthenticatedUser> {
    return new Promise((resolve) => {
      this.getOnStorage().then((res) => {
        if (res) {
          this.deleteOnStorage().then(() => {

          });
        }
      }).then(() => {
        this.updateUserService(user);
        this.storage.set('user', JSON.stringify(user));
        resolve();
      });
    });
  }

  /**
   * Get user properties from local storage.
   *
   * @returns {Promise<AuthenticatedUser>}
   */
  getOnStorage(): Promise<AuthenticatedUser> {
    return new Promise((resolve) => {
      this.updateUserService(JSON.parse(this.storage.get('user')));
      resolve(this.storage.get('user'));
    });
  }

  /**
   * Get user properties from local storage.
   *
   * @returns {Promise<AuthenticatedUser>}
   */
  getOnStorageSync() {
    this.updateUserService(JSON.parse(this.storage.get('user')));
    return this.storage.get('user');
  }

  /**
   * Update user properties from local storage.
   *
   * @param user
   * @returns {Promise<AuthenticatedUser>}
   */
  updateOnStorage(user: AuthenticatedUser): Promise<AuthenticatedUser> {
    return new Promise((resolve) => {
      resolve(this.storage.get('user'));
    });
  }

  /**
   * Delete user properties from local storage.
   *
   * @returns {Promise<AuthenticatedUser>}
   */
  deleteOnStorage(): Promise<AuthenticatedUser> {
    return new Promise((resolve) => {
      this.storage.clear();
      resolve();
    });
  }
}

The second service is ApiService. This service is use to send different HTTP methods and handled answers. In this service we will Format the header and access to the authenticatedUser's token.

@Injectable()
export class ApiService {

  private baseUrl = "https://my.api.co";
  private user: AuthenticatedUser;  


  /* ---------------------------------------------------------------------------------------------------------------- */

  constructor(private userService: UserService, private http: Http) {
     getAuthUser()
  }

   private getAuthUser() {
     this.userService.getOnStorage.then(
       (user) => {
         this.user = user;
       });      
   }

   /**
   * Get the Json Web Token from the local storage.
   *
   * @returns {RequestOptions}
   */
  private formatHeader(): RequestOptions {
    const headers: Headers = new Headers();
    if (this.user.token) {
      headers.append('Authorization', 'Bearer ' + this.user.token);
    }
    return new RequestOptions({headers});
  }

  /**
   * Get the body of an HTTP response.
   *
   * @param res
   * @returns {any|{}}
   */
  private handleBody(res: Response) {
    return res.json() || {};
  }

  /**
   * Format the error message of an HTTP response.
   *
   * @param error
   * @returns {any}
   */
  private handleError(error: Response | any) {
    let errorModel: any = {};

    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);

      errorModel = { status: error.status, message: `${error.status} - ${err.cause} ` };
    } else {
      errorModel = { status: error.status, message: error.toString()};
    }
    return Observable.throw(errorModel);
  }
  /* ---------------------------------------------------------------------------------------------------------------- */

  /**
   * Perform a PUT request.
   *
   * @param url
   * @param auth
   * @param body
   * @returns {Observable<>}
   */
  putRequest(url: string, body: Object, auth: boolean = true): Observable<Object> {
    let header = null;

    if (auth) {
      header = ApiService.formatHeader();
    }
    return this.http.put(this.BASE_URL + url, body, header)
      .map(ApiService.handleBody)
      .catch(ApiService.handleError);
  }

  /**
   * Perform a POST request.
   *
   * @param url
   * @param auth
   * @param body
   * @returns {Observable<>}
   */
  postRequest(url: string, body: Object, auth: boolean = true): Observable<Object> {
    let header = null;

    if (auth) {
      header = ApiService.formatHeader();
    }
    return this.http.post(this.BASE_URL + url, body, header)
      .map(ApiService.handleBody)
      .catch(ApiService.handleError);
  }

  /**
   * Perform a HEAD request.
   *
   * @param url
   * @param auth
   * @returns {Observable<>}
   */
  headRequest(url: string, auth: boolean = true): Observable<Object> {
    let header = null;

    if (auth) {
      header = ApiService.formatHeader();
    }
    return this.http.head(this.BASE_URL + url, header)
      .map(ApiService.handleBody)
      .catch(ApiService.handleError);
  }

  /**
   * Perform a GET request.
   *
   * @param url
   * @param auth
   * @returns {Promise<>}
   */
  getRequest(url: string, auth: boolean = true): Observable<Object> {
    let header = null

    if(auth) {
      header = ApiService.formatHeader();
    }

    return this.http.get(this.BASE_URL + url, header)
      .map(ApiService.handleBody)
      .catch(ApiService.handleError);
  }

  /**
   * Perform a DELETE request.
   *
   * @param url
   * @param auth
   * @returns {Observable<>}
   */
  deleteRequest(url: string, auth: boolean = true): Observable<Object> {
    let header = null;

    if (auth) {
      header = ApiService.formatHeader();
    }
    return this.http.delete(this.BASE_URL + url, header)
      .map(ApiService.handleBody)
      .catch(ApiService.handleError);
  }

And the last service is the AuthService. This service will send login/logout requests.

auth.service.ts :

@Injectable()
export class AuthService {
   private user: AuthenticatedUser;
  /* ---------------------------------------------------------------------------------------------------------------- */

  constructor(private userService: userService, private apiService: ApiService) {
  this.getAuthUser();
}
   getAuthUser() {
     this.userService.getOnStorage().then(
        (user) => {
          this.user = user;
        }
     );
   }

  /* ---------------------------------------------------------------------------------------------------------------- */

  /**
   * Request an authentication access.
   *
   * @param email the email of the user
   * @param password the password of the user
   * @returns {Promise<any>}
   */
  login(email: string, password: string): Promise<AuthentificatedUser> {
    return new Promise((resolve, reject) => {
      this.apiService.postRequest('/auth', {email: email, password: password})
        .subscribe(
          res => resolve(AuthentificatedUser.ParseFromObject(res)),
          error => reject(<any>error));
    });
  }

  /**
   * Logout a user from the authentication process.
   *
   * @returns {Promise<any>}
   */
  logout(): Promise<any> {
    return new Promise((resolve) => {
      this.userService.deleteOnStorage().then(() => {
        resolve();
      });
    });
  }


  /**
   * Check whether a user is already logged in.
   *
   * @returns {boolean}
   */
  isLoggedIn() {
    if (this.user.token) {
      return true;
    } else {
      return false;
    }
  }

  /* ---------------------------------------------------------------------------------------------------------------- */
}

In your login page :

this.authService.login('login', 'password').then(
  (user) => {
    this.userSerivce.createOnStorage(user);
    this.navCtrl.push(HomePage);
  }).catch(
    (err) => {
      //show the error
    }
  );

The last thing you have to do this to check if a user is connected or redirect the user to the login screen.

export HomePage() {
  private user: AuthenticatedUser;

  constructor(private userService: UserService) {
    this.getAuthUser();
  }

  private getAuthUser() {
    this.userService.getOnStorage().then(
      (user) => {
        this.user = user;

        if (!this.user.token) {
          this.navCtrl.push(LoginPage)
        }
      }
    );
  }
}
like image 191
panic Avatar answered Oct 18 '22 14:10

panic