Ich bin ein Fan von kleinen und sicheren Anwendungen, die ihre Daten ausschließlich lokal haben. Ohne Cloud, Online-Speicher oder Anmeldedaten.
Dann kommt allerdings die Anforderung, von mehreren Geräten aus auf den gleichen Datensatz zuzugreifen. Das ist ebenso sinnvoll, also beschäftige ich mich endlich mal damit, das ordentlich umzusetzen.

Das Problem

Es ist meine erste (eigene) Umsetzung von Benutzerkonten. 😀
Für meine WordProgressor Anwendung sollen sich Benutzer anmelden können, um Zugriff auf ihre eigenen Projekte zu erhalten.
Ich möchte aber auch, dass die App ohne Konto genutzt werden kann, einfach weil ich selbst es so lieber mag. So kann man als Benutzer erst einmal ausprobieren und bei Gefallen immer noch ein Konto anlegen.

Die vom Benutzer angelegten Projekte sollen nur ihm selbst zugänglich sein. Später möchte ich aber auch öffentliche Daten anbieten.

Das wichtigste – auch in diesem Beitrag – ist aber die Unterscheidung zwischen anonymem und autorisiertem Benutzer, sowie die Umwandlung.

Die Firebase-Lösung

Mein Udemy-Angular-Kurs: Angular – The Complete Guide (von Maximilian Schwarzmüller) setzt die Authentifizierung mit Firebase um. Da ich für meine andere App ohnehin schon Firebase nutze, kommt mir dieser Lösungsansatz entgegen.

Verwendet werden die Authentication-Funktion von Firebase, die komfortabel über die API angesprochen werden kann, sowie eine Realtime Database für die Nutzerdaten.
Auf das genaue Einrichten gehe ich hier nicht ein, sondern konzentriere mich auf die Schritte und Code-Stellen, die von der im Udemy-Kurs präsentierten Lösung abweichen.

Firebase-Einstellungen

Das Projekt muss bei Firebase eingerichtet sein. In den Projekteinstellungen gibt es einen Web-API-Schlüssel, der für Anfragen notwendig ist.
Außerdem wird eine Realtime Database mit den folgenden Regeln angelegt:

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

Die URL der Datenbank ist ebenfalls wichtig und im „Daten“-Tab abzulesen.

Das Programmieren

Für die Standard-Anmeldung gibt es einen AuthService, der http-Requests an die Firebase-API schickt.
Neben den Möglichkeiten für Login und Registrierung biete ich noch die Möglichkeit an, die Anwendung anonym zu nutzen.

Der lokale Speicher

Um Benutzerdaten zu speichern habe ich einen separaten DataStorageService erstellt, der abfragt, ob eine anonyme Session vorliegt und entsprechend entweder den localStorage beschreibt / ausliest, oder ein http-Request abschickt.
Exemplarisch einmal für die Anfrage von Projekten:

@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());
        });
  }
...
}

Benutzerdaten in der Datenbank

Um Nutzer-Exklusive Daten zu ermöglichen sorgen die oben erwähnten Regeln dafür, dass jeder Nutzer nur Zugriff auf das Verzeichnis mit seiner eigenen ID hat. Unterhalb dieses Verzeichnisses finden sich also die persönlichen Projekte und können auch nur vom jeweiligen Nutzer abgefragt werden.
Die Anfragen an die API enthält entsprechend die Nutzer-ID im Pfad.

Konto anlegen und Hochladen

In den Einstellungen hat der Benutzer die Möglichkeit, ein Konto anzulegen. Bereits angelegte (lokal gespeicherte) Projekte, werden dann dem neu registrierten Konto hinzu gefügt. Nach dem Erstellen eines Kontos können die Daten nicht mehr anonym genutzt werden, da sie aus dem lokalen Speicher entfernt werden.

Der anonyme Nutzer kann ein Konto mit seinen bisherigen Daten erstellen

Die Settings-Component ruft hierfür die Auth-Component mit einem zusätzlichen Parameter auf:

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

Dieser Parameter sorgt nun dafür, dass nur eine Registrierung (oder ein Abbruch) vorgenommen werden kann:

Auth-Component mit eingeschränkter Ansicht

Hier wird nun ein Konto angelegt, wie beim gewöhnlichen Registrieren, und im Anschluss die Projektdaten ebenfalls hinzugefügt.

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)
        }
      )
    });
  }

Das Ergebnis

Zum jetzigen Zeitpunkt bin ich noch nicht gänzlich damit zufrieden, wo sich welche Code-Blöcke befinden.
Für Interessierte und falls ihr den ganzen Code sehen wollt; das Git-Repo ist öffentlich und hier einzusehen.

Eine Folgefunktion wird nun das Synchronisieren und ggf. die Möglichkeit, die Daten trotzdem lokal vor zu halten und nur bei vorhandener Internetanbindung zu synchronisieren. Das kommt dann hoffentlich im Zuge eines PWA-Artikels.

Angular – Benutzerkonten mit Firebase Rest API

Beitragsnavigation


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert