So integrieren Sie OAuth 2 in Ihr Django/DRF-Backend, ohne verrückt zu werden

Veröffentlicht: 2022-03-11

Das haben wir alle schon durchgemacht. Sie arbeiten am API-Back-End und sind zufrieden damit, wie es läuft. Sie haben kürzlich das Minimal Viable Product (MVP) fertiggestellt, die Tests sind alle bestanden und Sie freuen sich darauf, einige neue Funktionen zu implementieren.

Dann schickt Ihnen der Chef eine E-Mail: „Übrigens müssen wir die Leute über Facebook und Google einloggen lassen; Sie sollten nicht nur für eine kleine Website wie unsere ein Konto erstellen müssen.“

Toll. Scope Creep schlägt erneut zu.

Die gute Nachricht ist, dass sich OAuth 2 zum Industriestandard für die Authentifizierung durch soziale Netzwerke und Drittanbieter (verwendet von Diensten wie Facebook, Google usw.) entwickelt hat, sodass Sie sich darauf konzentrieren können, diesen Standard zu verstehen und zu implementieren, um ein breites Spektrum an sozialen Netzwerken zu unterstützen Authentifizierungsanbieter.

Wahrscheinlich sind Sie mit OAuth 2 nicht vertraut; Ich war es nicht, als mir das passierte.

Integrieren Sie OAuth 2 in Ihr Django/DRF-Backend

Als Python-Entwickler kann Ihr Instinkt zu Pip führen, dem von Python Package Index (PyPA) empfohlenen Tool zum Installieren von Python-Paketen. Die schlechte Nachricht ist, dass Pip 278 Pakete kennt, die sich mit OAuth befassen – von denen 53 speziell Django erwähnen. Es ist eine Woche Arbeit wert, nur um die Optionen zu recherchieren, ganz zu schweigen davon, Code zu schreiben.

In diesem Tutorial erfahren Sie, wie Sie OAuth 2 mithilfe von Python Social Auth in Ihr Django- oder Django-Rest-Framework integrieren. Obwohl sich dieser Artikel auf das Django-REST-Framework konzentriert, können Sie die hier bereitgestellten Informationen anwenden, um dasselbe in einer Vielzahl anderer gängiger Back-End-Frameworks zu implementieren.

Ein kurzer Überblick über den OAuth 2-Flow

OAuth 2 wurde von Anfang an als Web-Authentifizierungsprotokoll konzipiert. Dies ist nicht ganz dasselbe, als ob es als Netzauthentifizierungsprotokoll entworfen worden wäre; Es wird davon ausgegangen, dass Ihnen Tools wie HTML-Rendering und Browserweiterleitungen zur Verfügung stehen.

Dies ist offensichtlich ein Hindernis für eine JSON-basierte API, aber Sie können dies umgehen.

Sie durchlaufen den Prozess so, als ob Sie eine herkömmliche, serverseitige Website schreiben würden.

Der serverseitige OAuth 2-Flow

Der erste Schritt findet vollständig außerhalb des Anwendungsflusses statt. Der Projekteigentümer muss Ihre Anwendung bei jedem OAuth 2-Anbieter registrieren, für den Sie Logins benötigen.

Während dieser Registrierung stellen sie dem OAuth 2-Anbieter eine Rückruf-URI bereit, unter der Ihre Anwendung für den Empfang von Anfragen verfügbar sein wird. Im Gegenzug erhalten sie einen Clientschlüssel und ein Clientgeheimnis . Diese Token werden während des Authentifizierungsprozesses ausgetauscht, um die Anmeldeanforderungen zu validieren.

Die Token beziehen sich auf Ihren Servercode als Client. Der Host ist der OAuth 2-Anbieter. Sie sind nicht für die Clients Ihrer API bestimmt.

Der Ablauf beginnt, wenn Ihre Anwendung eine Seite generiert, die eine Schaltfläche wie „Mit Facebook anmelden“ oder „Mit Google+ anmelden“ enthält. Das sind im Grunde nichts anderes als einfache Links, die jeweils auf eine URL wie die folgende verweisen:

 https://oauth2provider.com/auth? response_type=code& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email

(Hinweis: Zur besseren Lesbarkeit wurden Zeilenumbrüche in die URI oben eingefügt.)

Sie haben Ihren Clientschlüssel und Umleitungs-URI bereitgestellt, aber keine Geheimnisse. Im Gegenzug haben Sie dem Server mitgeteilt, dass Sie als Antwort einen Authentifizierungscode und Zugriff auf die Bereiche „Profil“ und „E-Mail“ wünschen. Diese Bereiche definieren die Berechtigungen, die Sie vom Benutzer anfordern, und begrenzen die Autorisierung des Zugriffstokens, das Sie erhalten.

Nach Erhalt wird der Browser des Benutzers auf eine dynamische Seite geleitet, die der OAuth 2-Anbieter steuert. Der OAuth 2-Anbieter überprüft, ob der Rückruf-URI und der Clientschlüssel übereinstimmen, bevor er fortfährt. Wenn dies der Fall ist, weicht der Fluss je nach Sitzungstoken des Benutzers kurz ab.

Wenn der Benutzer derzeit nicht bei diesem Dienst angemeldet ist, wird er dazu aufgefordert. Nach der Anmeldung wird dem Benutzer ein Dialogfeld angezeigt, in dem er um Erlaubnis gebeten wird, Ihrer Anwendung die Anmeldung zu gestatten.

Unter der Annahme, dass der Benutzer zustimmt, leitet der OAuth 2-Server ihn dann zurück zu der von Ihnen bereitgestellten Callback-URI, einschließlich eines Autorisierungscodes in den Abfrageparametern: GET https://api.yourapp.com/oauth2/callback/?code=AUTH_CODE .

Der Autorisierungscode ist ein schnell ablaufender, einmalig verwendbarer Token; Unmittelbar nach Erhalt sollte Ihr Server umkehren und eine weitere Anfrage an den OAuth 2-Anbieter stellen, die sowohl den Autorisierungscode als auch Ihr Client-Secret enthält:

 POST https://oauth2provider.com/token/? grant_type=authorization_code& code=AUTH_CODE& redirect_uri=CALLBACK_URI& client_id=CLIENT_KEY& client_secret=CLIENT_SECRET

Der Zweck dieses Autorisierungscodes besteht darin, die obige POST-Anforderung zu authentifizieren, aber aufgrund der Art des Datenflusses muss er durch das System des Benutzers geleitet werden. Als solches ist es von Natur aus unsicher.

Die Einschränkungen des Autorisierungscodes (dh dass er schnell abläuft und nur einmal verwendet werden kann) dienen dazu, das inhärente Risiko zu mindern, dass ein Authentifizierungsnachweis durch ein nicht vertrauenswürdiges System geleitet wird.

Dieser Aufruf, der direkt von Ihrem Server an den Server des OAuth-2-Anbieters erfolgt, ist die Schlüsselkomponente des serverseitigen OAuth-2-Anmeldevorgangs. Die Kontrolle des Anrufs bedeutet, dass Sie wissen, dass der Anruf TLS-gesichert ist, und hilft so, ihn vor Abhörangriffen zu schützen.

Durch die Angabe des Autorisierungscodes wird sichergestellt, dass der Benutzer ausdrücklich seine Zustimmung erteilt hat. Das Einbeziehen des geheimen Clientschlüssels, der für Ihre Benutzer niemals sichtbar ist, stellt sicher, dass diese Anfrage nicht von Viren oder Malware auf dem System des Benutzers stammt, die den Autorisierungscode abgefangen haben.

Stimmt alles überein, gibt der Server ein Zugriffstoken zurück, mit dem Sie diesen Anbieter anrufen können, während Sie als Benutzer authentifiziert sind.

Sobald Sie das Zugriffstoken vom Server erhalten haben, leitet Ihr Server den Browser des Benutzers erneut auf die Zielseite für Benutzer um, die sich gerade angemeldet haben. Es ist daher üblich, das Zugriffstoken im serverseitigen Sitzungscache des Benutzers zu behalten dass der Server bei Bedarf Anrufe an den jeweiligen sozialen Anbieter tätigen kann.

Der Zugriffstoken sollte dem Benutzer niemals zur Verfügung gestellt werden!

Es gibt mehr Details, in die wir eintauchen könnten.

Beispielsweise enthält Google ein Aktualisierungstoken , das die Lebensdauer Ihres Zugriffstokens verlängert, während Facebook einen Endpunkt bereitstellt, an dem Sie kurzlebige Zugriffstoken gegen etwas Langlebigeres austauschen können. Diese Details sind uns jedoch egal, da wir diesen Flow nicht verwenden werden.

Dieser Ablauf ist für eine REST-API umständlich. Während Sie den Front-End-Client die anfängliche Anmeldeseite generieren und das Back-End eine Rückruf-URL bereitstellen lassen könnten, werden Sie schließlich auf ein Problem stoßen. Sie möchten den Benutzer auf die Zielseite des Front-Ends umleiten, sobald Sie das Zugriffstoken erhalten haben, und es gibt keine klare, REST-konforme Möglichkeit, dies zu tun.

Glücklicherweise ist ein anderer OAuth-2-Flow verfügbar, der in diesem Fall viel besser funktioniert.

Der clientseitige OAuth 2-Flow

In diesem Ablauf ist das Front-End für die Abwicklung des gesamten OAuth 2-Prozesses verantwortlich. Es ähnelt im Allgemeinen dem serverseitigen Ablauf, mit einer wichtigen Ausnahme – Front-Ends befinden sich auf Computern, die von Benutzern kontrolliert werden, sodass ihnen das Client-Geheimnis nicht anvertraut werden kann. Die Lösung besteht darin, diesen gesamten Schritt des Prozesses einfach zu eliminieren.

Der erste Schritt ist wie im serverseitigen Ablauf die Registrierung der Anwendung.

In diesem Fall registriert der Projekteigentümer die Anwendung weiterhin, jedoch als Webanwendung. Der OAuth 2-Anbieter stellt weiterhin einen Clientschlüssel bereit, stellt jedoch möglicherweise kein Clientgeheimnis bereit.

Das Front-End stellt dem Benutzer eine Social-Login-Schaltfläche zur Verfügung, die zu einer Webseite führt, die der OAuth 2-Anbieter kontrolliert, und die Erlaubnis für unsere Anwendung anfordert, auf bestimmte Aspekte des Benutzerprofils zuzugreifen.

Die URL sieht diesmal allerdings etwas anders aus:

 https://oauth2provider.com/auth? response_type=token& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email

Beachten Sie, dass der Parameter response_type diesmal in der URL token ist.

Was ist also mit dem Umleitungs-URI?

Dies ist einfach eine beliebige Adresse am Front-End, die bereit ist, das Zugriffstoken entsprechend zu handhaben.

Abhängig von der verwendeten OAuth 2-Bibliothek kann das Front-End tatsächlich vorübergehend einen Server ausführen, der HTTP-Anforderungen auf dem Gerät des Benutzers annehmen kann; In diesem Fall hat die Weiterleitungs-URL das Format http://localhost:7862/callback/?token=TOKEN .

Da der OAuth 2-Server eine HTTP-Weiterleitung zurückgibt, nachdem der Benutzer dies akzeptiert hat, und diese Weiterleitung vom Browser auf dem Gerät des Benutzers verarbeitet wird, wird diese Adresse richtig interpretiert, wodurch das Front-End Zugriff auf das Token erhält.

Alternativ kann das Front-End direkt eine entsprechende Seite implementieren. In jedem Fall ist das Front-End an dieser Stelle für die Analyse der Abfrageparameter und die Verarbeitung des Zugriffstokens verantwortlich.

Ab diesem Zeitpunkt kann das Frontend die API des OAuth-2-Anbieters direkt mit dem Token aufrufen. Aber die Benutzer wollen das nicht wirklich; sie möchten einen authentifizierten Zugriff auf Ihre API. Alles, was das Back-End bereitstellen muss, ist ein Endpunkt, an dem das Front-End das Zugriffstoken eines sozialen Anbieters gegen ein Token austauschen kann, das Zugriff auf Ihre API gewährt.

Warum dies überhaupt zulassen, da die Bereitstellung des Zugriffstokens an das Front-End von Natur aus weniger sicher ist als der serverseitige Fluss?

Der clientseitige Ablauf ermöglicht eine strengere Trennung zwischen einer Back-End-REST-API und einem benutzerorientierten Front-End. Nichts hindert Sie strikt daran, Ihren Back-End-Server als Umleitungs-URI anzugeben. Der Endeffekt wäre eine Art hybrider Fluss.

Das Problem ist, dass der Server dann eine geeignete benutzerseitige Seite generieren und dann die Kontrolle auf irgendeine Weise an das Front-End zurückgeben muss.

In modernen Projekten ist es üblich, Bedenken zwischen der Front-End-Benutzeroberfläche und dem Back-End zu trennen, das die gesamte Geschäftslogik verarbeitet. Sie kommunizieren normalerweise über eine klar definierte JSON-API. Der oben beschriebene hybride Ablauf trübt diese Trennung der Bedenken jedoch und zwingt das Back-End, sowohl eine benutzerseitige Seite zu bedienen als auch einen Ablauf zu entwerfen, um die Kontrolle irgendwie zurück an das Front-End zu übergeben.

Dem Front-End zu erlauben, das Zugriffstoken zu handhaben, ist eine zweckmäßige Technik, die die Trennung von Bedenken beibehält. Es erhöht etwas das Risiko eines kompromittierten Clients, funktioniert aber im Allgemeinen gut.

Dieser Ablauf mag für das Front-End kompliziert erscheinen, und das ist er auch, wenn das Front-End-Team alles selbst entwickeln muss. Sowohl Facebook als auch Google stellen jedoch Bibliotheken bereit, die es dem Frontend ermöglichen, Anmeldeschaltflächen einzubinden, die den gesamten Prozess mit einer minimalen Konfiguration abwickeln.

Hier ist ein Rezept für den Token-Austausch im Back-End.

Unter dem Client-Flow ist das Back-End ziemlich isoliert vom OAuth-2-Prozess. Lassen Sie sich nicht täuschen: Dies ist keine einfache Aufgabe. Sie sollten mindestens die folgenden Funktionen unterstützen.

  • Senden Sie mindestens eine Anfrage an den OAuth 2-Anbieter, nur um sicherzustellen, dass das vom Front-End bereitgestellte Token gültig war und nicht irgendeine zufällige Zeichenfolge.
  • Wenn das Token gültig ist, geben Sie ein gültiges Token für Ihre API zurück. Geben Sie andernfalls einen informativen Fehler zurück.
  • Wenn es sich um einen neuen Benutzer handelt, erstellen Sie ein User für ihn und füllen Sie es entsprechend aus.
  • Wenn es sich um einen Benutzer handelt, für den bereits ein User vorhanden ist, gleichen Sie ihn mit seiner E-Mail-Adresse ab, damit er Zugriff auf das richtige vorhandene Konto erhält, anstatt ein neues Konto für die soziale Anmeldung zu erstellen.
  • Aktualisieren Sie die Profildetails des Benutzers basierend auf dem, was er in sozialen Medien bereitgestellt hat.

Die gute Nachricht ist, dass die Implementierung all dieser Funktionen im Backend viel einfacher ist, als Sie vielleicht erwarten.

Hier ist die Magie, wie man all dies in nur zwei Dutzend Codezeilen am Backend zum Laufen bringt. Dies hängt von der Python Social Auth-Bibliothek (nachfolgend „PSA“) ab, daher müssen Sie sowohl social-auth-core als auch social-auth-app-django in Ihre requirements.txt aufnehmen.

Außerdem müssen Sie die Bibliothek wie hier dokumentiert konfigurieren. Beachten Sie, dass dies aus Gründen der Übersichtlichkeit einige Ausnahmebehandlungen ausschließt.

Den vollständigen Code für dieses Beispiel finden Sie hier.

 @api_view(http_method_names=['POST']) @permission_classes([AllowAny]) @psa() def exchange_token(request, backend): serializer = SocialSerializer(data=request.data) if serializer.is_valid(raise_exception=True): # This is the key line of code: with the @psa() decorator above, # it engages the PSA machinery to perform whatever social authentication # steps are configured in your SOCIAL_AUTH_PIPELINE. At the end, it either # hands you a populated User model of whatever type you've configured in # your project, or None. user = request.backend.do_auth(serializer.validated_data['access_token']) if user: # if using some other token back-end than DRF's built-in TokenAuthentication, # you'll need to customize this to get an appropriate token object token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key}) else: return Response( {'errors': {'token': 'Invalid token'}}, status=status.HTTP_400_BAD_REQUEST, )

Es muss nur noch ein bisschen mehr in Ihre Einstellungen (vollständiger Code) gehen, und dann sind Sie fertig:

 AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', 'social_core.backends.facebook.FacebookOAuth2', 'django.contrib.auth.backends.ModelBackend', ) for key in ['GOOGLE_OAUTH2_KEY', 'GOOGLE_OAUTH2_SECRET', 'FACEBOOK_KEY', 'FACEBOOK_SECRET']: # Use exec instead of eval here because we're not just trying to evaluate a dynamic value here; # we're setting a module attribute whose name varies. exec("SOCIAL_AUTH_{key} = os.environ.get('{key}')".format(key=key)) SOCIAL_AUTH_PIPELINE = ( 'social_core.pipeline.social_auth.social_details', 'social_core.pipeline.social_auth.social_uid', 'social_core.pipeline.social_auth.auth_allowed', 'social_core.pipeline.social_auth.social_user', 'social_core.pipeline.user.get_username', 'social_core.pipeline.social_auth.associate_by_email', 'social_core.pipeline.user.create_user', 'social_core.pipeline.social_auth.associate_user', 'social_core.pipeline.social_auth.load_extra_data', 'social_core.pipeline.user.user_details', )

Fügen Sie dieser Funktion in Ihrer urls.py eine Zuordnung hinzu, und schon sind Sie fertig!

Wie funktioniert diese Magie?

Python Social Auth ist eine sehr coole, sehr komplexe Maschinerie. Es kümmert sich gerne um die Authentifizierung und den Zugriff auf einen von mehreren Dutzend Social-Authentifizierungsanbietern und funktioniert auf den meisten gängigen Python-Web-Frameworks, einschließlich Django, Flask, Pyramid, CherryPy und WebPy.

Zum größten Teil ist der obige Code eine sehr standardmäßige funktionsbasierte Ansicht des Django-REST-Frameworks (DRF): Er wartet auf POST-Anfragen auf dem Pfad, dem Sie ihn in Ihrer urls.py , und, vorausgesetzt, Sie senden ihm eine Anfrage in der erwarteten Format erhältst du dann ein User -Objekt oder None .

Wenn Sie ein User erhalten, handelt es sich um den Modelltyp, den Sie an anderer Stelle in Ihrem Projekt konfiguriert haben und der möglicherweise bereits vorhanden war oder nicht. PSA hat sich bereits darum gekümmert, das Token zu validieren, festzustellen, ob eine Benutzerübereinstimmung existierte, einen Benutzer zu erstellen, falls erforderlich, und Benutzerdetails vom sozialen Anbieter zu aktualisieren.

Die genauen Details, wie ein Benutzer vom Benutzer des sozialen Anbieters zu Ihrem zugeordnet und mit bestehenden Benutzern verknüpft wird, werden durch die oben definierte SOCIAL_AUTH_PIPELINE angegeben. Es gibt noch viel mehr darüber zu erfahren, wie das alles funktioniert, aber das würde den Rahmen dieses Beitrags sprengen. Hier können Sie mehr darüber lesen.

Der Schlüssel zur Magie ist der @psa() Dekorator in der Ansicht, der dem request , das an Ihre Ansicht übergeben wird, einige Mitglieder hinzufügt. Am interessantesten ist für uns request.backend (für PSA ist ein Backend jeder Anbieter für soziale Authentifizierung).

Das geeignete Backend wurde für uns ausgewählt und basierend auf dem backend -Argument für die Ansicht an das request angehängt, das von der URL selbst gefüllt wird.

Sobald Sie das backend -Objekt in der Hand haben, authentifiziert es Sie gerne bei diesem Anbieter, wenn Sie Ihren Zugangscode erhalten. das ist die do_auth -Methode. Dies wiederum greift auf die gesamte SOCIAL_AUTH_PIPELINE aus Ihrer Konfigurationsdatei zurück.

Die Pipeline kann einige ziemlich mächtige Dinge tun, wenn Sie sie erweitern, obwohl sie bereits alles tut, was Sie brauchen, mit nichts als ihrer standardmäßigen, integrierten Funktionalität.

Danach geht es einfach zurück zum normalen DRF-Code: Wenn Sie ein gültiges User erhalten haben, können Sie ganz einfach ein entsprechendes API-Token zurückgeben. Wenn Sie kein gültiges User zurückerhalten, können Sie leicht einen Fehler generieren.

Ein Nachteil dieser Technik besteht darin, dass es zwar relativ einfach ist, Fehler zurückzugeben, wenn sie auftreten, es jedoch schwierig ist, genaue Einblicke in das zu erhalten, was genau schief gelaufen ist. PSA schluckt alle Details, die der Server möglicherweise über das Problem zurückgegeben hat.

Andererseits liegt es in der Natur gut gestalteter Authentifizierungssysteme, Fehlerquellen ziemlich undurchsichtig zu machen. Wenn eine Anwendung einem Benutzer nach einem Anmeldeversuch jemals „Ungültiges Passwort“ mitteilt, ist das gleichbedeutend mit „Herzlichen Glückwunsch! Sie haben einen gültigen Benutzernamen erraten.“

Warum nicht einfach selber rollen?

Mit einem Wort: Erweiterbarkeit. Nur sehr wenige Social-OAuth-2-Anbieter verlangen oder geben genau die gleichen Informationen in ihren API-Aufrufen auf genau die gleiche Weise zurück. Es gibt jedoch alle möglichen Sonderfälle und Ausnahmen.

Das Hinzufügen eines neuen sozialen Anbieters, nachdem Sie bereits einen PSA eingerichtet haben, ist eine Frage einiger Konfigurationszeilen in Ihren Einstellungsdateien. Sie müssen überhaupt keinen Code anpassen. PSA abstrahiert all das, damit Sie sich auf Ihre eigene Anwendung konzentrieren können.

Wie um alles in der Welt teste ich das?

Gute Frage! unittest.mock ist nicht gut geeignet, um API-Aufrufe zu verspotten, die unter einer Abstraktionsschicht tief in einer Bibliothek verborgen sind; Allein den genauen Weg zum Spotten zu finden, würde erhebliche Anstrengungen erfordern.

Da PSA auf der Requests-Bibliothek aufgebaut ist, verwenden Sie stattdessen die ausgezeichnete Responses-Bibliothek, um die Anbieter auf HTTP-Ebene zu verspotten.

Eine vollständige Erörterung des Testens würde den Rahmen dieses Artikels sprengen, aber ein Beispiel unserer Tests ist hier enthalten. Besonders zu beachtende Funktionen sind der mocked Context Manager und die SocialAuthTests -Klasse.

Lassen Sie PSA die schwere Arbeit erledigen.

Der OAuth2-Prozess ist detailliert und kompliziert mit viel inhärenter Komplexität. Glücklicherweise ist es möglich, einen Großteil dieser Komplexität zu umgehen, indem Sie eine Bibliothek einbringen, die darauf ausgerichtet ist, sie so schmerzlos wie möglich zu handhaben.

Python Social Auth leistet dabei hervorragende Arbeit. Wir haben eine Django/DRF-Ansicht demonstriert, die den clientseitigen, impliziten OAuth2-Fluss verwendet, um eine nahtlose Benutzererstellung und einen nahtlosen Abgleich in nur 25 Codezeilen zu erreichen. Das ist nicht zu schäbig.