Jak Mogę używać Django OAuth Toolkit z Pythonem Social Auth?

Buduję API używając Django Rest Framework. Później API ma być zużywane przez urządzenia z systemem iOS i Android. Chcę, aby moi użytkownicy mogli zarejestrować się u dostawców oauth2, takich jak Facebook i Google. W takim przypadku nie powinni w ogóle tworzyć konta na mojej platformie. Ale użytkownicy powinni mieć również możliwość rejestracji, gdy nie mają konta Facebook / Google,dla którego używam django-OAuth-toolkit, więc mam własnego OAuth2-provider.

Dla zewnętrznych dostawców, których używam python-social-auth, który działa dobrze i automatycznie tworzy obiekty użytkownika.

Chcę, aby klienci uwierzytelniali się za pomocą tokenów okaziciela, co działa dobrze dla użytkowników, którzy zarejestrowali się u mojego dostawcy (django-OAuth-toolkit zapewnia schemat uwierzytelniania i klasy uprawnień dla Django Rest Framework).
jednak python-social-auth implementuje tylko uwierzytelnianie oparte na sesji, więc nie ma prostego sposobu na wysyłanie uwierzytelnionych zapytań API w imieniu zarejestrowanych użytkowników przez zewnętrznego dostawcę oauth2.

Jeśli używam access_token wygenerowanego przez django - OAuth-toolkit, wykonanie takiego żądania działa:

curl -v -H "Authorization: Bearer <token_generated_by_django-oauth-toolkit>" http://localhost:8000/api/

Jednak poniższe nie działa, ponieważ nie ma odpowiedniego schematu uwierzytelniania dla Django Rest Framework, a AUTHENTICATION_BACKENDS dostarczane przez python-social-auth działają tylko dla uwierzytelniania opartego na sesji:

curl -v -H "Authorization: Bearer <token_stored_by_python-social-auth>" http://localhost:8000/api/

Używanie przeglądalnego API dostarczonego przez Django Rest Framework po uwierzytelnieniu za pomocą python-social-auth działa dobrze, tylko połączenia API bez pliku cookie sesji nie działają.

Zastanawiam się, jakie jest najlepsze podejście do tego problemu. Z tego co widzę, mam w zasadzie dwie opcje:

A: gdy użytkownik zarejestruje się u zewnętrznego dostawcy oauth2( obsługiwanego przez python-social-auth), podłącz się do procesu, aby utworzyć oauth2_provider.modelki.AccessToken i nadal używać 'oauth2_provider.ext.rest_framework.OAuth2Authentication', Teraz uwierzytelnia również użytkowników zarejestrowanych u zewnętrznego dostawcy. Takie podejście jest sugerowane tutaj: https://groups.google.com/d/msg/django-rest-framework/ACKx1kY7kZM/YPWFA2DP9LwJ

B: użyj python-social-auth do uwierzytelniania żądań API. Mogłem wprowadzić własnych użytkowników do python-social-auth, pisząc Niestandardowy backend i używając register_by_access_token. Jednakże, ponieważ wywołania API nie mogą wykorzystywać sesji Django, oznaczałoby to, że musiałbym napisać schemat uwierzytelniania dla Django Rest Framework, który wykorzystuje dane przechowywane przez python-social-auth. Niektóre wskazówki jak to zrobić można znaleźć tutaj:
http://psa.matiasaguirre.net/docs/use_cases.html#signup-by-oauth-access-token
http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/
http://cbdev.blogspot.it/2014/02/facebook-login-with-angularjs-django.html
Jednak sposób, w jaki to Rozumiem python-social-auth weryfikuje token tylko podczas logowania i opiera się na sesji Django. To by oznaczało, że aby znaleźć sposób, aby uniemożliwić python-social-auth wykonywanie całego oauth2-flow dla każdego bezstanowego żądania API i raczej sprawdzić dane przechowywane w DB, który nie jest tak naprawdę zoptymalizowany do zapytań, ponieważ jest przechowywany jako JSON (mógłbym użyć UserSocialAuth.obiektów.get (extra_data__contains=) though).
Musiałbym również zająć się weryfikacją zakresów tokena dostępu i użyć ich do sprawdzenia uprawnień, co django-oauth-toolkit już robi (TokenHasScope, required_scopes itp.).

W moment, skłaniam się ku użyciu opcji A, ponieważ django-OAuth-toolkit zapewnia dobrą integrację z Django Rest Framework i dostaję wszystko, czego potrzebuję po wyjęciu z pudełka. Jedyną wadą jest to, że muszę "wstrzyknąć" access_tokens pobrany przez python-social-auth do modelu accesstoken django-oauth-toolkit, co wydaje się w jakiś sposób złe, ale prawdopodobnie byłoby zdecydowanie najłatwiejszym podejściem.

Czy ktoś ma jakieś zastrzeżenia co do tego, czy może rozwiązał ten sam problem w inaczej? Czy przegapiłem coś oczywistego i utrudniłem sobie życie? Jeśli ktoś już zintegrował django-OAuth-toolkit z python-social-auth i zewnętrznymi dostawcami OAuth2 byłbym bardzo wdzięczny za kilka wskazówek lub opinii.

Author: Kevin Brown, 2014-11-21

3 answers

Duża trudność w implementacji OAuth sprowadza się do zrozumienia, jak ma działać przepływ autoryzacji. Dzieje się tak głównie dlatego, że jest to" punkt wyjścia " do logowania, a podczas pracy z zewnętrznym backendem (używając czegoś takiego jak Python Social Auth) robisz to dwa razy : raz dla swojego API i raz dla zewnętrznego API.

Autoryzowanie żądań za pomocą interfejsu API i zewnętrznego zaplecza

Proces uwierzytelniania że trzeba przejść to:

Schemat sekwencji dla opcji A

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Facebook : User signs in
Facebook -> Django Login : User authorizes your API
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app

używam "Facebook" jako backend strony trzeciej tutaj, ale proces jest taki sam dla każdego backend.

Z punktu widzenia Twojej aplikacji mobilnej, przekierowujesz tylko na/authorize URL dostarczony przez Django OAuth Toolkit . Stamtąd aplikacja mobilna czeka, aż zostanie osiągnięty URL wywołania zwrotnego, podobnie jak w standardowym przepływie autoryzacji OAuth. Prawie wszystko inne (Django login, logowanie społecznościowe itp.) jest obsługiwany przez Django OAuth Toolkit lub Python Social Auth w tle.

Będzie to również kompatybilne z praktycznie wszystkimi bibliotekami OAuth, których używasz, a przepływ autoryzacji będzie działał tak samo, bez względu na to, jaki backend stron trzecich jest używany. Poradzi sobie nawet z (powszechnym) przypadkiem, w którym musisz być w stanie obsługiwać backend uwierzytelniania Django (email/nazwa użytkownika i hasło), jak również stronę trzecią login.

Opcja a bez zewnętrznego zaplecza

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app
Należy również pamiętać, że aplikacja mobilna (którą może być każdy klient OAuth) nigdy nie otrzymuje tokenów Facebook/OAuth innych firm. Jest to niezwykle ważne, ponieważ zapewnia, że API działa jako pośrednik między Klientem OAuth a kontami społecznościowymi użytkownika.

Schemat sekwencji z interfejsem API jako strażnikiem

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives OAuth token
Mobile App -> Your API : Requests the display name
Your API -> Facebook : Requests the full name
Facebook -> Your API : Sends back the full name
Your API -> Mobile App : Send back a display name

W przeciwnym razie klient OAuth byłby w stanie ominąć Twoje API i złożyć żądania na Twoim w imieniu do interfejsów API stron trzecich.

Schemat sekwencji do ominięcia API

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives Facebook token
Mobile App -> Facebook : Requests all of the followers
Facebook -> Mobile App : Sends any requested data

Zauważysz, że w tym momencie straciłbyś całą kontrolę nad tokenami stron trzecich . Jest to szczególnie niebezpieczne, ponieważ większość tokenów może uzyskać dostęp do szerokiego zakresu danych, co otwiera drzwi do nadużyć i ostatecznie spada pod Twoim imieniem . Najprawdopodobniej osoby logujące się do twojego API/strony nie zamierzały udostępniać swoich informacji społecznościowych klientowi OAuth i były zamiast tego oczekujesz, że utrzymasz te informacje w tajemnicy (tak bardzo, jak to możliwe), ale zamiast tego ujawniasz te informacje wszystkim.

Uwierzytelnianie zapytań do twojego API

Kiedy aplikacja mobilna używa twojego tokena OAuth do wysyłania zapytań do twojego API , całe uwierzytelnianie odbywa się za pomocą zestawu narzędzi Django OAuth (lub Twojego dostawcy OAuth) w tle. wszystko, co widzisz, to to, że istnieje User związane z Twoim Prośba.

Jak sprawdzane są tokeny OAuth

Mobile App -> Your API : Sends request with OAuth token
Your API -> Django OAuth Toolkit : Verifies the token
Django OAuth Toolkit -> Your API : Returns the user who is authenticated
Your API -> Mobile App : Sends requested data back

Jest to ważne, ponieważ po etapie autoryzacji nie powinno mieć znaczenia, czy użytkownik pochodzi z Facebook ' a czy z systemu uwierzytelniania Django . Twój API potrzebuje tylko User do pracy, a twój dostawca OAuth powinien być w stanie obsłużyć uwierzytelnianie i weryfikację tokenu.

To niewiele różni się od tego, jak Django Rest framework uwierzytelnia użytkownika, gdy używa wspieranych sesji uwierzytelnianie.

Diagram sekwencji do uwierzytelniania za pomocą sesji

Web Browser -> Your API : Sends session cookie
Your API -> Django : Verifies session token
Django -> Your API : Returns session data
Your API -> Django : Verifies the user session
Django -> Your API : Returns the logged in user
Your API -> Web Browser : Returns the requested data

Ponownie, wszystko to jest obsługiwane przez Django OAuth Toolkit i nie wymaga dodatkowej pracy do implementacji.

Praca z natywnym SDK

W większości przypadków będziesz uwierzytelniać użytkownika za pośrednictwem własnej strony internetowej i używać Python Social Auth do obsługi wszystkiego. Jednak jedynym zauważalnym wyjątkiem jest użycie natywnego SDK, ponieważ uwierzytelnianie i autoryzacja jest obsługiwane przez natywny system, co oznacza , że całkowicie omijasz swoje API. Jest to idealne rozwiązanie dla aplikacji, które muszą zalogować się u osób trzecich, lub aplikacji, które w ogóle nie używają API, ale to koszmar, gdy oba łączą się.

Dzieje się tak dlatego, że Twój serwer nie może zweryfikować loginu i jest zmuszony założyć, że login jest poprawny i prawdziwy , co oznacza, że omija wszelkie zabezpieczenia, które daje Python Social Auth ty.

Używanie natywnego zestawu SDK może powodować problemy

Mobile App -> Facebook SDK : Opens the authorization prompt
Facebook SDK -> Mobile App : Gets the Facebook token
Mobile App -> Your API : Sends the Facebook token for authorization
Your API -> Django Login : Tries to validate the token
Django Login -> Your API : Returns a matching user
Your API -> Mobile App : Sends back an OAuth token for the user

Zauważysz, że to przeskakuje API podczas fazy uwierzytelniania, a następnie zmusza API do przyjęcia założeń dotyczących tokena, który jest przekazywany. Ale są na pewno przypadki, w których to ryzyko może być tego warte, więc powinieneś to ocenić przed wyrzuceniem. Jest to kompromis pomiędzy szybkim i natywnym logowaniem użytkownika i potencjalnie obsługującym złe lub złośliwe tokeny.

 82
Author: Kevin Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-25 15:29:14

Rozwiązałem to używając Twojej opcji A.

To, co robię, to rejestracja użytkowników, którzy używają strony trzeciej do rejestracji za pomocą tokenu dostępu strony trzeciej.

url(r'^register-by-token/(?P<backend>[^/]+)/$',
    views.register_by_access_token),

W ten sposób mogę wysłać żądanie GET, takie jak to:

GET http://localhost:8000/register-by-token/facebook/?access_token=123456

I register_by_access_token zostaje wezwany. request.backend.do_auth zapyta dostawcę o informacje o użytkowniku z tokenu i magicznie zarejestruje konto Użytkownika za pomocą informacji lub zaloguje użytkownika, jeśli jest już zarejestrowany.

Następnie tworzę token ręcznie i zwracam go jako JSON za umożliwienie klientowi zapytania mojego API.

from oauthlib.common import generate_token
...
@psa('social:complete')
def register_by_access_token(request, backend):
    # This view expects an access_token GET parameter, if it's needed,
    # request.backend and request.strategy will be loaded with the current
    # backend and strategy.
    third_party_token = request.GET.get('access_token')
    user = request.backend.do_auth(third_party_token)

    if user:
        login(request, user)

        # We get our app!   
        app = Application.objects.get(name="myapp")

        # We delete the old token
        try:
            old = AccessToken.objects.get(user=user, application=app)
        except:
            pass
        else:
            old.delete()

        # We create a new one
        my_token = generate_token()

        # We create the access token 
        # (we could create a refresh token too the same way) 
        AccessToken.objects.create(user=user,
                                   application=app,
                                   expires=now() + timedelta(days=365),
                                   token=my_token)

        return "OK" # you can return your token as JSON here

    else:
        return "ERROR"

Po prostu nie jestem pewien, jak generuję token, czy to dobra praktyka? W międzyczasie działa!!

 9
Author: Felix D.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-06 20:02:26

Może django-rest-Framework-social-OAuth2 jest tym, czego szukasz. Ten pakiet zależy od python-social-auth i django-oauth-toolkit, których już używasz. Szybko przeskanowałem dokumentację i wydaje się, że zaimplementuje to, co próbujesz zrobić.

 5
Author: Serrano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-13 15:22:23