Dissecting Authentication from a Big Fat Angular Application

von Patrick Bédat, 6. Juli 2014

We recently released a „big fat“ (far beyond 10k LOC)  web application, with a front end purely written in javascript. Since it was the first big js only web app we wrote, it’s time to recap and share our lessons learned.

What I want to talk about right now, is how to treat authentication (based on cookies and http basic-auth) as what it really is: an aspect. I am absolutely sure everyone of us once made the mistake of weaving a cross functional aspect, like authentication, authorization or logging into parts of the application where they don’t belong, thus making it less readable and less testable.

Well we did not make this mistake. We put all the authentication stuff to a shared angular module and connected it with restangular in the config() section of our app. What really went wrong was the dependency between authorization and authentication in the routing module (we are using ui-router).

TL;DR: Move authentication and the rest of your app in separate apps, that coexist on one page – it’s cleaner.

Let me give you a simple example:

First Come First Served

You have the following nested state in your application:

Refreshing your browser, when you are in state „wf.employee.salary“, will cause you some trouble: You have to make sure, that your app is already authenticated, before you resolve the „employee“, otherwise the authorization will throw a 403 at you.

Of course there are many ways to overcome this. One way is to resolve the login process in the root state and reference it as a dependency, where you resolve authentication sensitive data:

Even then you would have to handle with a not authenticated client in an untransparent way. You know – redirect to a login, save the requested url, and so on…

A Clean Cut

My new approach is to fully segregate the whole login process from the rest of the application.

This is my recipe:

Have two applications on your site: One that handles authentication (bootstrapper would be a convenient name) and your main application. The „bootstrapper“ will handle authentication and bootstrap the main app, once authentication suceeded.

Move your authentication api to the bootstrapping application. A naive implementation may look like this:

In this example I initialize the authentication in the xsLoginForm directive. This is also the place, where I handle the bootstrap of the main application:

You could also .configure() your HTTP-Client with the required authentication headers, before you bootstrap your main app.

Fresher Refreshing

Now let’s take a look to back to what caused us trouble in the first place: Refreshing the page. Instead of a dependency stricken login process, the main app now does not even care about authentication. After our bootstrapping app has handled all it’s authentication needs and the main app is loaded, the ui-router will kick in, examine the url and will nicely resolve the state it held, before the refresh.

 

 

Angular.js für Azubis

von Patrick Bédat, 9. März 2014

Angular zu erlernen ist aufgrund der steilen Lernkurve auch für erfahrene Entwickler keine einfache Aufgabe.
Für Auszubildende ist der Umgang mit angular gleich doppelt schwierig: Steile Lernkurve gepaart mit absoluter Freiheit funktioniert zwar,
führt bei Code Reviews aber zu hoher Frustration.

So ergeht es auch gerade unserem Auszubildenen Michael. Nicht nur, dass er sich im ersten Lehrjahr befindet, nein auch Patterns, Paradigmen und Datenstrukturen sind ihm völlig Fremd. Deshalb verstreut er auch nur zu recht die Geschäftslogik munter zwischen Controllern und allen anderen Komponenten.

Ich habe selbst immer wieder erlebt, welche Wunder die Lektüre von Quellcode anderer Entwickler wirken kann und möchte mit dieser gebloggten Coding-Session meinem Azubi und hoffentlich auch anderen Einsteigern eine kleine Hilfestellung bieten, angular.js Apps richtig zu strukturieren.

Ausgangssituation

Michael soll ein Projekt in angular.js umsetzen. Wir lassen ihn dabei zunächst so viel Geschäftslogik, wie möglich, auf dem Client umsetzen, damit er sich nicht um weitere Technologien Gedanken machen muss. Ein Framework ermöglicht es ihm Datensätze in CRUD Manier zu speichern, zu lesen und zu löschen.

Momentan sieht das grobe Datenmodell der Anwendung so aus:

UML

Michael sollte dafür sorgen, dass die Testfälle eines Projekts als Tests einem Test-Cycle zugeordnet werden können:

screen1

Die Testfälle zur Linken können über einen Pfeil-Button als Test dem aktuellen Test-Cycle zugeordnet werden. Diese Tests können ebenfalls über einen Pfeilbutton wieder wieder aus dem Test-Cycle entfernt werden.

Template

Werfen wir zunächst mal einen Blick auf das Template:


— Was soll ich groß dazu sagen? Nichts besonderes dabei – nur standard Direktiven und keine Zauberei.

Controller

Bei Initialisierung wird zunächst mit dem GetCycle Service ein Test-Cycle abgerufen. Anhand der Tests im Test-Cycle werden die verfügbaren Testfälle dem $scope zugeordnet. —

Der Controller vermittelt zwischen dem Template und dem Model mit Hilfe folgender Schnittstelle:

  • AssignTestcase
    Weist dem Cycle den aktuell selektierten Testfall zu
  • Event: cycle:test:added
    Wird ein Test dem Cycle hinzugefügt, wird dieser Event ausgelöst.  Der Controller entfernt daraufhin den selektierten Testfall aus der Liste der zur Verfügung stehenden Testfälle
  • Event: cycle:test:remove
    Wird ein Test wieder aus dem Cycle entfernt wird dieser Event ausgelöst. Der Controller fügt dann den Testcase, der dem Test zugordnet ist, wieder der Liste der zur Verfügung stehenden Testfälle hinzu.
  • Save
    Speichert die Änderungen am Cycle ab

Models

Die Models sorgen dafür, dass wir unsere Daten mit Respekt behandeln. Sie bieten koordinierte Zugriffe auf unsere Testfälle, Tests, Cylces und sorgen z.B. für die korrekte Erstellung von Tests. Häufig verwenden Models den $rootScope dazu um Ereignisse abzusetzen, auf die Controller wiederum reagieren können.

SelectableCollection


— Mit dieser Collection kann jeweils immer ein Item (z.B. Tests oder Testfälle) selektiert werden dann entfernt werden. Nachdem ein Item entfernt wurde, wird das nächste in der Liste selektiert. Beim hinzufügen und entfernen setzt diese Collection außerdem Events in den $rootScope ab.

CycleModel

Dieses Model nutzt auch die SelectableCollection, um die Tests zu verwalten. Mit dem Model können auch Tests erstellt werden. Diese werden hier vereinfacht durch das zuordnen eines Testfalls erzeugt. —

Services

In den Services befindet sich Geschäftslogik, die sich weniger mit Datenstrukturen und Zuständen beschäftigt. Da wird beispielsweise mit anderen APIs oder dem Backend kommuniziert, es werden Daten transformiert oder Models instanziert.

GetCycle

Dieser Service ruft den gewünschten Cylce, alle Tests und Testfälle aus dem Backend ab. Danach wird der Cycle mit den zugeordneten Tests ausgestattet und jedem Test wird sein referenzierter Testfall zugeordnet.

Diese komische Konstruktion mit dem „_“. Das gehört zu einem meiner absoluten Lieblings-Frameworks: Underscore


 GetTestcasesAvailableToCycle

Die Testfälle, die dem Cycle bereits zugeordnet wurden, sollen nicht für den Cycle zur Verfügung stehen: —

GetTestcasesByProject

Wie der Name schon sagt:



 

Zusammenfassung

Ich hoffe ich konnte mit dieser kleiner Coding-Session ein paar Aha-Effekte bei euch generieren. Den gesamten ausführbaren (z.B. cd build/ && xsp) Code findet ihr übrigends hier:

https://github.com/pbedat/xAmine.explained