I’m a huge fan of small, secure applications with only local data storage. Without cloud, online-storage or credentials.
But then there ist he feature of using several devices to access the same set of data. That’s useful as well. Therefore I finally get my hands on it.

The problem

This is my first (own) implementation of user accounts. 😀
For my WordProgressor application, users should be able to login and gain access to their very own projects.
But I also want the app to be useable without an account, just because I myself like it that way. This makes it possible to first give it a try and if I like it, I can still create an account.

Projects created by the user should only be accessible by himself. Later I will add public data too.

Most important ist he differentiation between anonymous and autorized user, as well as the transition.

The Firebase solution

In my udemy angular course Angular – The Complete Guide (by Maximilian Schwarzmüller)  authentification is implemented using firebase. I already use firebase for another app, therefore I liked the solution.

Firebase authentification function is in use, which can be comfortably accessed via API, as well as a realtime database for the user data.
I won’t cover a detailed howto on the setup but concentrate on the steps and code snippets that differ from the solution in the udemy course above.

Firebase settings

The project must be created within firebase. Under project settings, there is a web api key needed fort he requests.
Additionally, a realtime database with the following rules is created:

{
  "rules": {
    "$uid": {
      ".write": "$uid === auth.uid",
      ".read": "$uid === auth.uid"
    }
  }
}

The database URL is important as well and can be found within the data tab.

The coding

For the standard login, there is an AuthService that sends http request to the firebase API.
Besides login and registration I also offer an anonymous usage of the app.

Local storage

To save user data I created a separate DataStorageService who asks wheather there is an anonymous session and therefore reads / writes tot he local storage or sends an http request.
As a showcase, here is the fetch project method:

@Injectable({
  providedIn: "root"
})
export class DataStorageService {

projects: Project[] = [];
  public projectList = new Subject<Project[]>();
  user: userData = {};
  public user$ = new Subject<userData>();

  constructor(
    private http: HttpClient,
    private authService: AuthService,
  ) {
    this.authService.checkAnonymous();
  }

  get isAnonymous(): boolean | null {
    return this.authService.isAnonymous;
  }

  fetchProjects() {
    this.projects = [];
    if (this.isAnonymous) {
      this._fetchProjectsFromStorage();
    } else {
      this._fetchProjectsFromAPI();
    }
  }

  _fetchProjectsFromStorage() {
    const projects: Project[] = JSON.parse(<string>localStorage.getItem('projects'));
    if (!!projects) this.projects = projects;
    this.projectList.next(this.projects.slice());
  }

  _fetchProjectsFromAPI() {
    this.http.get<{ [key: string]: Project }>(
      environment.FIREBASE_DB_URL+this.authService.userId+'/projects.json'
    )
      .pipe(
        take(1),
        map((responseData) => {
          const projectArray: Project[] = [];
          for (const key in responseData) {
            if (responseData.hasOwnProperty(key)) {
              projectArray.push({ ...responseData[key], id:key });
            }
          }
          return projectArray;
        }),
        catchError(errorRes => {
          return throwError(errorRes);
        })
      )
      .subscribe(
        (projects) => {
          this.projects = projects;
          this.projectList.next(projects.slice());
        });
  }
...
}

User data inside the database

To allow user exclusive data, the rules mentioned above only allow access to the directory with the ID of the authenticated user. Below are the personal projects who also can only be accessed by the owner.
Requests tot he API therefore contain the user ID in it’s path.

Register account and upload

The users settings allow the creation of an account. Projects already created (locally) will be added tot he newly registered account. Afterwards they can’t be accessed anonymously, because they were removed from the local storage.

The anonymous user can create an account containing his current data

The Settings-Component calls the Auth-Component with an additional parameter to perform the adjusted registration:

createAccount() {
    this.router.navigate(['/auth'],{queryParams: { mode: 'createFromLocal'}});
  }

This parameter takes care, that only registration (or cancel) can be performed at this step:

Auth-Component with restricted view

Just like regular registration, an account is created and the project data is added afterwards.

private _uploadLocalData() {
    const id = this.authService.userId;
    let projects: Project[] = JSON.parse(<string>localStorage.getItem('projects'));
    projects.map(project => {
      this.http.post<any>(
        environment.FIREBASE_DB_URL + id + '/projects.json',
        project
      ).subscribe(() => {
          localStorage.removeItem('projects');
        },
        error => {
          console.log(error)
        }
      )
    });
  }

The result

Right now I’m not fully content with the placement of certain code snippets.
For those who are interested or if you want to see the whole code; the git repo is public and can be visited here.

A follow up will be synchronization and the possibility to save authenticated user data to sync when an internet connection is available. Hopefully this will follow in a PWA post.

Angular – User Accounts with Firebase Rest API

Post navigation


Leave a Reply

Your email address will not be published.