{"id":502,"date":"2021-09-28T08:05:16","date_gmt":"2021-09-28T08:05:16","guid":{"rendered":"https:\/\/erdbeerbeet.com\/?p=502"},"modified":"2021-10-26T21:03:37","modified_gmt":"2021-10-26T21:03:37","slug":"angular-user-accounts-with-firebase","status":"publish","type":"post","link":"https:\/\/erdbeerbeet.com\/de\/angular-user-accounts-with-firebase","title":{"rendered":"Angular &#8211; Benutzerkonten mit Firebase Rest API"},"content":{"rendered":"\n<p><a><\/a>Ich bin ein Fan von kleinen und sicheren Anwendungen, die ihre Daten ausschlie\u00dflich lokal haben. Ohne Cloud, Online-Speicher oder Anmeldedaten.<br>Dann kommt allerdings die Anforderung, von mehreren Ger\u00e4ten aus auf den gleichen Datensatz zuzugreifen. Das ist ebenso sinnvoll, also besch\u00e4ftige ich mich endlich mal damit, das ordentlich umzusetzen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Das Problem<\/h2>\n\n\n\n<p>Es ist meine erste (eigene) Umsetzung von Benutzerkonten. \ud83d\ude00<br>F\u00fcr meine <a href=\"https:\/\/erdbeerbeet.com\/projekte\/wordprogressor\">WordProgressor <\/a>Anwendung sollen sich Benutzer anmelden k\u00f6nnen, um Zugriff auf ihre eigenen Projekte zu erhalten.<br>Ich m\u00f6chte 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.<\/p>\n\n\n\n<p>Die vom Benutzer angelegten Projekte sollen nur ihm selbst zug\u00e4nglich sein. Sp\u00e4ter m\u00f6chte ich aber auch \u00f6ffentliche Daten anbieten.<\/p>\n\n\n\n<p>Das wichtigste &#8211; auch in diesem Beitrag &#8211; ist aber die Unterscheidung zwischen anonymem und autorisiertem Benutzer, sowie die Umwandlung.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Die Firebase-L\u00f6sung<\/h2>\n\n\n\n<p>Mein Udemy-Angular-Kurs: <a href=\"https:\/\/www.udemy.com\/course\/the-complete-guide-to-angular-2\/\">Angular &#8211; The Complete Guide (von Maximilian Schwarzm\u00fcller)<\/a> setzt die Authentifizierung mit Firebase um. Da ich f\u00fcr meine andere App ohnehin schon Firebase nutze, kommt mir dieser L\u00f6sungsansatz entgegen.<\/p>\n\n\n\n<p>Verwendet werden die <a href=\"https:\/\/firebase.google.com\/docs\/reference\/rest\/auth\">Authentication-Funktion<\/a> von Firebase, die komfortabel \u00fcber die API angesprochen werden kann, sowie eine <a href=\"https:\/\/firebase.google.com\/docs\/reference\/rest\/database\">Realtime Databas<\/a>e f\u00fcr die Nutzerdaten.<br>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\u00e4sentierten L\u00f6sung abweichen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Firebase-Einstellungen<\/h3>\n\n\n\n<p>Das <strong>Projekt<\/strong> muss bei Firebase eingerichtet sein. In den Projekteinstellungen gibt es einen <strong>Web-API-Schl\u00fcssel<\/strong>, der f\u00fcr Anfragen notwendig ist.<br>Au\u00dferdem wird eine <strong>Realtime Database<\/strong> mit den folgenden Regeln angelegt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"rules\": {\n    \"$uid\": {\n      \".write\": \"$uid === auth.uid\",\n      \".read\": \"$uid === auth.uid\"\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Die<strong> URL der Datenbank<\/strong> ist ebenfalls wichtig und im &#8220;Daten&#8221;-Tab abzulesen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Das Programmieren<\/h2>\n\n\n\n<p>F\u00fcr die Standard-Anmeldung gibt es einen AuthService, der http-Requests an die Firebase-API schickt.<br>Neben den M\u00f6glichkeiten f\u00fcr Login und Registrierung biete ich noch die M\u00f6glichkeit an, die Anwendung anonym zu nutzen.<\/p>\n\n\n\n<figure class=\"wp-block-gallery columns-3 is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"499\" height=\"252\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen1.png\" alt=\"\" data-id=\"508\" data-link=\"https:\/\/erdbeerbeet.com\/?attachment_id=508\" class=\"wp-image-508\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen1.png 499w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen1-300x152.png 300w\" sizes=\"auto, (max-width: 499px) 100vw, 499px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"423\" height=\"316\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen2.png\" alt=\"\" data-id=\"509\" data-link=\"https:\/\/erdbeerbeet.com\/?attachment_id=509\" class=\"wp-image-509\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen2.png 423w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen2-300x224.png 300w\" sizes=\"auto, (max-width: 423px) 100vw, 423px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"503\" height=\"376\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen3.png\" alt=\"\" data-id=\"510\" data-full-url=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen3.png\" data-link=\"https:\/\/erdbeerbeet.com\/?attachment_id=510\" class=\"wp-image-510\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen3.png 503w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen3-300x224.png 300w\" sizes=\"auto, (max-width: 503px) 100vw, 503px\" \/><\/figure><\/li><\/ul><figcaption class=\"blocks-gallery-caption\">Nutzung als anonymer Benutzer<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Der lokale Speicher<\/h3>\n\n\n\n<p>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.<br>Exemplarisch einmal f\u00fcr die Anfrage von Projekten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Injectable({\n  providedIn: \"root\"\n})\nexport class DataStorageService {\n\nprojects: Project&#91;] = &#91;];\n  public projectList = new Subject&lt;Project&#91;]&gt;();\n  user: userData = {};\n  public user$ = new Subject&lt;userData&gt;();\n\n  constructor(\n    private http: HttpClient,\n    private authService: AuthService,\n  ) {\n    this.authService.checkAnonymous();\n  }\n\n  get isAnonymous(): boolean | null {\n    return this.authService.isAnonymous;\n  }\n\n  fetchProjects() {\n    this.projects = &#91;];\n    if (this.isAnonymous) {\n      this._fetchProjectsFromStorage();\n    } else {\n      this._fetchProjectsFromAPI();\n    }\n  }\n\n  _fetchProjectsFromStorage() {\n    const projects: Project&#91;] = JSON.parse(&lt;string&gt;localStorage.getItem('projects'));\n    if (!!projects) this.projects = projects;\n    this.projectList.next(this.projects.slice());\n  }\n\n  _fetchProjectsFromAPI() {\n    this.http.get&lt;{ &#91;key: string]: Project }&gt;(\n      environment.FIREBASE_DB_URL+this.authService.userId+'\/projects.json'\n    )\n      .pipe(\n        take(1),\n        map((responseData) =&gt; {\n          const projectArray: Project&#91;] = &#91;];\n          for (const key in responseData) {\n            if (responseData.hasOwnProperty(key)) {\n              projectArray.push({ ...responseData&#91;key], id:key });\n            }\n          }\n          return projectArray;\n        }),\n        catchError(errorRes =&gt; {\n          return throwError(errorRes);\n        })\n      )\n      .subscribe(\n        (projects) =&gt; {\n          this.projects = projects;\n          this.projectList.next(projects.slice());\n        });\n  }\n...\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Benutzerdaten in der Datenbank<\/h3>\n\n\n\n<p>Um Nutzer-Exklusive Daten zu erm\u00f6glichen sorgen die oben erw\u00e4hnten Regeln daf\u00fcr, dass jeder Nutzer nur Zugriff auf das Verzeichnis mit seiner eigenen ID hat. Unterhalb dieses Verzeichnisses finden sich also die pers\u00f6nlichen Projekte und k\u00f6nnen auch nur vom jeweiligen Nutzer abgefragt werden.<br>Die Anfragen an die API enth\u00e4lt entsprechend die Nutzer-ID im Pfad.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Konto anlegen und Hochladen<\/h3>\n\n\n\n<p>In den Einstellungen hat der Benutzer die M\u00f6glichkeit, ein Konto anzulegen. Bereits angelegte (lokal gespeicherte) Projekte, werden dann dem neu registrierten Konto hinzu gef\u00fcgt. Nach dem Erstellen eines Kontos k\u00f6nnen die Daten nicht mehr anonym genutzt werden, da sie aus dem lokalen Speicher entfernt werden.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-duotone-2\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"289\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen4.png\" alt=\"\" class=\"wp-image-511\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen4.png 500w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen4-300x173.png 300w\" sizes=\"auto, (max-width: 500px) 100vw, 500px\" \/><figcaption>Der anonyme Nutzer kann ein Konto mit seinen bisherigen Daten erstellen<\/figcaption><\/figure>\n\n\n\n<p>Die Settings-Component ruft hierf\u00fcr die Auth-Component mit einem zus\u00e4tzlichen Parameter auf:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>createAccount() {\n    this.router.navigate(&#91;'\/auth'],{queryParams: { mode: 'createFromLocal'}});\n  }<\/code><\/pre>\n\n\n\n<p>Dieser Parameter sorgt nun daf\u00fcr, dass nur eine Registrierung (oder ein Abbruch) vorgenommen werden kann:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-duotone-2\"><img loading=\"lazy\" decoding=\"async\" width=\"502\" height=\"302\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen5.png\" alt=\"\" class=\"wp-image-512\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen5.png 502w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2021\/09\/wp_screen5-300x180.png 300w\" sizes=\"auto, (max-width: 502px) 100vw, 502px\" \/><figcaption>Auth-Component mit eingeschr\u00e4nkter Ansicht<\/figcaption><\/figure>\n\n\n\n<p>Hier wird nun ein Konto angelegt, wie beim gew\u00f6hnlichen Registrieren, und im Anschluss die Projektdaten ebenfalls hinzugef\u00fcgt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private _uploadLocalData() {\n    const id = this.authService.userId;\n    let projects: Project&#91;] = JSON.parse(&lt;string&gt;localStorage.getItem('projects'));\n    projects.map(project =&gt; {\n      this.http.post&lt;any&gt;(\n        environment.FIREBASE_DB_URL + id + '\/projects.json',\n        project\n      ).subscribe(() =&gt; {\n          localStorage.removeItem('projects');\n        },\n        error =&gt; {\n          console.log(error)\n        }\n      )\n    });\n  }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Das Ergebnis<\/h2>\n\n\n\n<p>Zum jetzigen Zeitpunkt bin ich noch nicht g\u00e4nzlich damit zufrieden, wo sich welche Code-Bl\u00f6cke befinden.<br>F\u00fcr Interessierte und falls ihr den ganzen Code sehen wollt; das Git-Repo ist \u00f6ffentlich und <a href=\"https:\/\/github.com\/Fenja\/word-progressor\" class=\"broken_link\">hier<\/a> einzusehen.<\/p>\n\n\n\n<p>Eine Folgefunktion wird nun das Synchronisieren und ggf. die M\u00f6glichkeit, die Daten trotzdem lokal vor zu halten und nur bei vorhandener Internetanbindung zu synchronisieren. Das kommt dann hoffentlich im Zuge eines PWA-Artikels.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ein Benutzerkonto sollte f\u00fcr mich erst einmal optional sein. Wenn ich eine Anwendung anonym ausprobiert habe erstelle ich dann ein Konto, um meine Daten online zu speichern.<\/p>\n","protected":false},"author":1,"featured_media":384,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41,12,36,49],"tags":[44,53,52,45],"class_list":["post-502","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-programming","category-web-programming","category-word_progressor","tag-angular","tag-api","tag-firebase","tag-word-progressor"],"translation":{"provider":"WPGlobus","version":"3.0.0","language":"de","enabled_languages":["en","de"],"languages":{"en":{"title":true,"content":true,"excerpt":true},"de":{"title":true,"content":true,"excerpt":true}}},"_links":{"self":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/502","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/comments?post=502"}],"version-history":[{"count":24,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/502\/revisions"}],"predecessor-version":[{"id":599,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/502\/revisions\/599"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/media\/384"}],"wp:attachment":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/media?parent=502"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/categories?post=502"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/tags?post=502"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}