Motivation

Angular ist ein leistungsstarkes Frontend-Framework zur Erstellung anspruchsvoller Single Page Applications (SPA). Mit Flask können SPAs sehr einfach geladen werden, wie hier erklärt wird. Die Integration gebräuchlicher Sicherheitsmaßnahmen, die in SPAs verwendet werden, erfordert jedoch eine extra Aufwand, weshalb zusätzliche Erweiterungen erforderlich sind. Eine sehr verbreitete Erweiterung ist flask-security, welche eine Menge Funktionalität für die Handhabung von Authentifizierung, Autorisierung, CSRF-Schutz und vieles mehr bietet.

Die Nutzung von flask-security zur Handhabung von SPAs ist jedoch etwas heikel, da sich flask-security mehr auf Formulare konzentriert und eine Vielzahl von Anpassungen enthält, die während der Initialisierung vorgenommen werden müssen. Deshalb möchte ich zeigen, welche Anpassungen vorgenommen werden müssen, um eine sichere und moderne SPA mit Flask und Angular zu erzeugen.

Beispielapplikation

Wir haben eine einfache Demo-Anwendung implementiert, mit deren Hilfe Blogger Nachrichtenartikel schreiben und anzuzeigen können. Benutzer können sich registrieren, in ihr Konto einloggen und wieder ausloggen. Ähnlich wie bei GitHub wollen wir das Einloggen entweder per Benutzername oder Passwort erlauben.
Die Frontend- und Backend-Logik sind sauber getrennt. Der Frontend-Code besteht aus HTML- und JavaScript-Code, der unabhängig vom Backend-Code geladen werden könnte (z.B. durch Nginx). Das Backend ist eine Flask REST API, die lediglich als Datenschnittstelle zur Datenbank dient.

Die Angular Loginform schaut folgendermaßen aus:
 Und die Hauptform mit der Darstellung der Nachrichtenartikel, welche der Blogger bis dato geschrieben hat:

Flask Implementierung

Im Folgenden möchte ich die wichtigsten Teile des Flask-Backends erklären, die für die Angular-Integration benötigt werden.
Beginnen wir zunächst mit der Architektur der Datenbankmodelle. Das Datenbankschema besteht aus Modellen zum Speichern von Benutzern und Rollen:

from flask_security import UserMixin, RoleMixin, SQLAlchemyUserDatastore

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean)
    confirmed_at = db.Column(db.DateTime)
    roles = db.relationship(
        "Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic")
    )


class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(40))
    description = db.Column(db.String(255))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)

Das UserMixin and RoleMixin bieten zusätzliche Funktionalität für die Zuweisung von Rollen und Berechtigungen an Benutzern. Die Klasse SQLAlchemyUserDatastore agiert als ein Proxy für die SQLAlchemy Datenbank.

 

Als nächstes muss eine Security-Instanz von flask-security erzeugt werden:

    flask_wtf.CSRFProtect(app)
    Security(
        app,
        user_datastore,
        confirm_register_form=ExtendedRegisterForm,
        login_form=ExtendedLoginForm,
    )

Zunächst wird der CSRF-Schutz durch den Aufruf von flask_wtf.CSRFProtect(app) aktiviert. Dieser gibt ein CSRF-Cookie zurück, sobald sich ein Benutzer eingeloggt hat. Angular hängt dieses Cookie automatisch an jeden weiteren POST Request an. Der Security Initialisierer nimmt das Flask app Objekt, den erstellten Datastore und die benutzerdefinierten Formulare für die Registrierung und Anmeldung. Wir müssen benutzerdefinierte Formulare erstellen, da flask-security standardmäßig nur die Registrierung und Anmeldung per E-Mail unterstützt. Da wir zusätzlich die Anmeldung per Benutzername unterstützen wollen, müssen wir die Standardformulare überschreiben.

Besondere Vorsicht ist bei ungültigen Anfragen geboten. flask-security gibt standardmäßig Flask WTF-Formulare zurück, wenn der Content-Type-Header text/html ist. Dies ist per Postman leicht zu beobachten:

Da wir unsere eigenen Formulare in Angular implementiert haben, benötigen wir diese Formulare nicht. Wir müssen also sicherstellen, dass
die Anfragen den richtigen Content-Header haben. Außerdem wollen wir sicherstellen, dass der Typ der Anfragemethode POST ist. Wenn wir zum Beispiel GET als Anfragemethodentyp für die Anmeldung verwenden, gibt Flask ein CSRF-Cookie zurück, obwohl die Anmeldung nicht erfolgreich war. Dies kann wiederum in Postman angezeigt werden:

Dieses CSRF-Token wird einem anonymen Benutzer zugewiesen, der keine Berechtigungen hat und sich an keinem Endpunkt autorisieren kann. In der Praxis stellt dies also keine Sicherheitslücke dar, aber es ist dennoch gute Praxis, eine Fehlermeldung statt eines unbrauchbaren CSRF-Tokens zurückzugeben.

Die Views werden mit flask-restful implementiert.
Zum Beispiel  sieht der Endpunkt, der für die Rückgabe eines bestimmten Artikels verwendet wird folgendermaßen aus:

class ArticleGetView(Resource):
    @auth_required()
    def get(self, id):
        article = db.session.query(Article).filter_by(id=id).first()
        if not article:
            return "Not found.", 404
        return {"id": article.id, "content": article.content}, 200

Wir haben bereits in anderen Blog-Artikeln gezeigt, wie diese Erweiterung verwendet wird, daher gibt es nicht viel zu erklären. Der einzige wichtige Teil ist der @auth_required()-Dekorator, der von flask-security zur Verfügung gestellt wird. Dieser Dekorator stellt sicher, dass nur eingeloggte Benutzer Zugriff auf ihre Artikel haben.

Zusammenfassung

In diesem Blog-Artikel haben wir gezeigt, wie flask-security eingesetzt werden kann, um SPAs mittels Angular zu liefern. Ein wichtiger Teil, den wir ausgelassen haben, sind die Konfigurationsparameter, die gesetzt werden müssen, um Flask und Angular reibungsfrei zu kombinieren. Es ist jedoch nicht notwendig, diese Parameter im Detail zu erklären, da die Verwendung dieser Parameter in der __init_.py-Datei im Repo gut beschrieben ist.

Der Quellcode für dieses Projekt ist hier zu finden.

Abonnieren
Benachrichtige mich bei
guest
0 Comments
Inline Feedbacks
View all comments