Der Autor des Artikels, dessen Übersetzung wir heute veröffentlichen, sagt, dass Sie jetzt die wachsende Beliebtheit von Authentifizierungsdiensten wie Google Firebase Authentication, AWS Cognito und Auth0 beobachten können. Generische Lösungen wie passport.js sind zum Industriestandard geworden. Angesichts der aktuellen Situation ist es jedoch üblich geworden, dass Entwickler nie vollständig verstehen, welche Mechanismen beim Betrieb von Authentifizierungssystemen eine Rolle spielen.
Dieses Material befasst sich mit dem Problem der Organisation der Benutzerauthentifizierung in Node.js. In einem praktischen Beispiel werden die Organisation der Benutzerregistrierung im System und die Organisation ihres Eintritts in das System betrachtet. Es werden Probleme wie die Arbeit mit JWT-Technologie und der Identitätswechsel von Benutzern aufgeworfen.

Beachten Sie auch
dieses GitHub-Repository, das den Code für das Node.js-Projekt enthält. Einige Beispiele hierfür finden Sie in diesem Artikel. Sie können dieses Repository als Grundlage für Ihre eigenen Experimente verwenden.
Projektanforderungen
Hier sind die Anforderungen für das Projekt, mit dem wir uns hier befassen werden:
- Das Vorhandensein einer Datenbank, in der die E-Mail-Adresse und das Kennwort des Benutzers gespeichert werden, entweder clientId und clientSecret oder eine Kombination aus privaten und öffentlichen Schlüsseln.
- Verwenden eines starken und effizienten kryptografischen Algorithmus zum Verschlüsseln eines Kennworts.
Im Moment, in dem ich dieses Material schreibe, glaube ich, dass der beste der vorhandenen kryptografischen Algorithmen Argon2 ist. Ich bitte Sie, keine einfachen kryptografischen Algorithmen wie SHA256, SHA512 oder MD5 zu verwenden.
Außerdem schlage ich vor, dass Sie sich
dieses wunderbare Material ansehen, in dem Sie Details zur Auswahl eines Algorithmus für das Hashing von Passwörtern finden.
Registrierung von Benutzern im System
Wenn ein neuer Benutzer im System erstellt wird, muss sein Kennwort gehasht und in der Datenbank gespeichert werden. Das Kennwort wird zusammen mit der E-Mail-Adresse und anderen Informationen über den Benutzer in der Datenbank gespeichert (z. B. das Benutzerprofil, die Registrierungszeit usw.).
import * as argon2 from 'argon2'; class AuthService { public async SignUp(email, password, name): Promise<any> { const passwordHashed = await argon2.hash(password); const userRecord = await UserModel.create({ password: passwordHashed, email, name, }); return {
Die Benutzerkontoinformationen sollten ungefähr so aussehen.
Benutzerdaten, die mit Robo3T aus MongoDB abgerufen wurdenBenutzeranmeldung
Hier ist ein Diagramm der Aktionen, die ausgeführt werden, wenn ein Benutzer versucht, sich anzumelden.
BenutzeranmeldungFolgendes passiert, wenn sich ein Benutzer anmeldet:
- Der Client sendet dem Server eine Kombination aus der öffentlichen Kennung und dem privaten Schlüssel des Benutzers. Dies ist normalerweise eine E-Mail-Adresse und ein Passwort.
- Der Server sucht nach dem Benutzer in der Datenbank anhand der E-Mail-Adresse.
- Wenn der Benutzer in der Datenbank vorhanden ist, hascht der Server das an ihn gesendete Kennwort und vergleicht das, was passiert ist, mit dem in der Datenbank gespeicherten Kennwort-Hash.
- Wenn die Überprüfung erfolgreich ist, generiert der Server ein sogenanntes Token oder Authentifizierungstoken - JSON Web Token (JWT).
JWT ist ein temporärer Schlüssel. Der Client muss diesen Schlüssel bei jeder Anforderung an den authentifizierten Endpunkt an den Server senden.
import * as argon2 from 'argon2'; class AuthService { public async Login(email, password): Promise<any> { const userRecord = await UserModel.findOne({ email }); if (!userRecord) { throw new Error('User not found') } else { const correctPassword = await argon2.verify(userRecord.password, password); if (!correctPassword) { throw new Error('Incorrect password') return { user: { email: userRecord.email, name: userRecord.name, }, token: this.generateJWT(userRecord), }
Die Kennwortüberprüfung wird mithilfe der argon2-Bibliothek durchgeführt. Dies soll die sogenannten "
Zeitangriffe " verhindern. Bei einem solchen Angriff versucht ein Angreifer, das Kennwort mit brutaler Gewalt zu knacken, basierend auf einer Analyse, wie viel Zeit der Server benötigt, um eine Antwort zu erstellen.
Lassen Sie uns nun darüber sprechen, wie JWT generiert wird.
Was ist ein JWT?
JSON Web Token (JWT) ist ein JSON-Objekt, das in Zeichenfolgenform codiert ist. Tokens können als Ersatz für Cookies verwendet werden, was gegenüber ihnen mehrere Vorteile hat.
Der Token besteht aus drei Teilen. Dies ist der Header, die Nutzlast und die Signatur. Die folgende Abbildung zeigt das Erscheinungsbild.
JwtToken-Daten können auf der Client-Seite ohne Verwendung eines geheimen Schlüssels oder einer geheimen Signatur dekodiert werden.
Dies kann nützlich sein, um beispielsweise Metadaten zu übertragen, die im Token codiert sind. Solche Metadaten können die Rolle des Benutzers, sein Profil, die Dauer des Tokens usw. beschreiben. Sie können für den Einsatz in Front-End-Anwendungen vorgesehen sein.
So könnte ein dekodierter Token aussehen.
Decodiertes TokenJWT in Node.js generieren
Lassen Sie uns die Funktion
generateToken
erstellen, die wir benötigen, um die Arbeit am Benutzerauthentifizierungsdienst abzuschließen.
Sie können JWT mithilfe der jsonwebtoken-Bibliothek erstellen. Sie finden diese Bibliothek in npm.
import * as jwt from 'jsonwebtoken' class AuthService { private generateToken(user) { const data = { _id: user._id, name: user.name, email: user.email }; const signature = 'MySuP3R_z3kr3t'; const expiration = '6h'; return jwt.sign({ data, }, signature, { expiresIn: expiration }); }
Das Wichtigste dabei sind die verschlüsselten Daten. Senden Sie keine geheimen Benutzerinformationen in Token.
Eine Signatur (hier ist die
signature
) sind die geheimen Daten, die zum Generieren der JWT verwendet werden. Es ist sehr wichtig sicherzustellen, dass die Unterschrift nicht in die falschen Hände gerät. Wenn die Signatur kompromittiert wird, kann der Angreifer im Namen der Benutzer Token generieren und deren Sitzungen stehlen.
Endpoint Protection und JWT-Validierung
Jetzt muss der Client-Code bei jeder Anforderung eine JWT an einen sicheren Endpunkt senden.
Es wird empfohlen, JWT in die Anforderungsheader aufzunehmen. Sie sind normalerweise im Authorization-Header enthalten.
AutorisierungsheaderJetzt müssen Sie auf dem Server Code erstellen, der Middleware für Expressrouten ist.
isAuth.ts
Sie diesen Code in die Datei
isAuth.ts
:
import * as jwt from 'express-jwt';
Es ist hilfreich, vollständige Informationen über das Benutzerkonto aus der Datenbank abrufen und an die Anforderung anhängen zu können. In unserem Fall wird diese Funktion mithilfe von Middleware aus der Datei
attachCurrentUser.ts
implementiert. Hier ist der vereinfachte Code:
export default (req, res, next) => { const decodedTokenData = req.tokenData; const userRecord = await UserModel.findOne({ _id: decodedTokenData._id }) req.currentUser = userRecord; if(!userRecord) { return res.status(401).end('User not found') } else { return next(); }
Nach der Implementierung dieses Mechanismus können Routen Informationen über den Benutzer empfangen, der die Anforderung ausführt:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import ItemsModel from '../models/items'; export default (app) => { app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => { const user = req.currentUser; const userItems = await ItemsModel.find({ owner: user._id }); return res.json(userItems).status(200); })
Die Route für
inventory/personal-items
ist jetzt geschützt. Um darauf zugreifen zu können, muss der Benutzer über eine gültige JWT verfügen. Eine Route kann außerdem Benutzerinformationen verwenden, um die Datenbank nach den benötigten Informationen zu durchsuchen.
Warum sind Token vor Eindringlingen geschützt?
Nachdem Sie die Verwendung von JWT gelesen haben, stellen Sie sich möglicherweise die folgende Frage: „Wenn JWT-Daten auf der Clientseite dekodiert werden können, ist es dann möglich, das Token so zu verarbeiten, dass die Benutzer-ID oder andere Daten geändert werden?“.
Token-Dekodierung - der Vorgang ist sehr einfach. Sie können dieses Token jedoch nicht "wiederholen", ohne diese Signatur zu haben, die geheimen Daten, die beim Signieren des JWT auf dem Server verwendet wurden.
Deshalb ist der Schutz dieser sensiblen Daten so wichtig.
Unser Server überprüft die Signatur in der isAuth-Middleware. Die Express-JWT-Bibliothek ist für die Überprüfung verantwortlich.
Nachdem wir herausgefunden haben, wie die JWT-Technologie funktioniert, lassen Sie uns nun einige interessante zusätzliche Funktionen besprechen, die sie uns bietet.
Wie kann man sich als Benutzer ausgeben?
Der Benutzeridentitätswechsel ist eine Technik, mit der Sie sich als bestimmter Benutzer bei einem System anmelden, ohne sein Kennwort zu kennen.
Diese Funktion ist sehr nützlich für Superadministratoren, Entwickler oder Supportmitarbeiter. Mit dem Identitätswechsel können sie Probleme lösen, die nur bei Benutzern auftreten, die mit dem System arbeiten.
Sie können im Namen des Benutzers mit der Anwendung arbeiten, ohne sein Passwort zu kennen. Dazu reicht es aus, eine JWT mit der richtigen Signatur und den erforderlichen Metadaten zu generieren, die den Benutzer beschreiben.
Erstellen Sie einen Endpunkt, der unter dem Deckmantel bestimmter Benutzer Token für die Eingabe in das System generieren kann. Nur der Superadministrator des Systems kann diesen Endpunkt verwenden.
Für den Anfang müssen wir diesem Benutzer eine Rolle mit einer höheren Berechtigungsstufe als anderen Benutzern zuweisen. Dies kann auf viele verschiedene Arten erfolgen. Fügen Sie beispielsweise einfach das Rollenfeld zu den in der Datenbank gespeicherten Benutzerinformationen hinzu.
Es kann wie das unten gezeigte aussehen.
Neues Feld in den BenutzerinformationenDer Wert des
super-admin
ist
super-admin
.
Als Nächstes müssen Sie eine neue Middleware erstellen, die die Benutzerrolle überprüft:
export default (requiredRole) => { return (req, res, next) => { if(req.currentUser.role === requiredRole) { return next(); } else { return res.status(401).send('Action not allowed'); }
Es sollte nach isAuth und attachCurrentUser platziert werden. Erstellen Sie nun den Endpunkt, der die JWT für den Benutzer generiert, für den sich der Superadministrator anmelden möchte:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import roleRequired from '../middlwares/roleRequired'; import UserModel from '../models/user'; export default (app) => { app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => { const userEmail = req.body.email; const userRecord = await UserModel.findOne({ email: userEmail }); if(!userRecord) { return res.status(404).send('User not found'); return res.json({ user: { email: userRecord.email, name: userRecord.name }, jwt: this.generateToken(userRecord) }) .status(200); })
Wie Sie sehen können, gibt es nichts Geheimnisvolles. Der Superadministrator kennt die E-Mail-Adresse des Benutzers, für den Sie sich anmelden möchten. Die Logik des obigen Codes erinnert sehr an die Funktionsweise des Codes und liefert eine Eingabe für das System normaler Benutzer. Der Hauptunterschied besteht darin, dass das Passwort hier nicht überprüft wird.
Das Passwort wird hier nicht verifiziert, da es hier einfach nicht benötigt wird. Die Endpunktsicherheit wird durch Middleware bereitgestellt.
Zusammenfassung
Es ist nichts Falsches daran, sich auf Authentifizierungsdienste und Bibliotheken von Drittanbietern zu verlassen. Dies hilft Entwicklern, Zeit zu sparen. Sie müssen jedoch auch wissen, auf welchen Prinzipien der Betrieb von Authentifizierungssystemen basiert und was die Funktionsweise solcher Systeme sicherstellt.
In diesem Artikel haben wir die Möglichkeiten der JWT-Authentifizierung untersucht und über die Bedeutung der Auswahl eines guten kryptografischen Algorithmus für das Hashing von Passwörtern gesprochen. Wir haben die Erstellung eines Benutzeridentitätswechselmechanismus untersucht.
Dasselbe mit so etwas wie passport.js zu tun, ist alles andere als einfach. Authentifizierung ist ein großes Thema. Vielleicht kehren wir zu ihr zurück.
Liebe Leser! Wie erstellen Sie Authentifizierungssysteme für Ihre Node.js-Projekte?