web-dev-qa-db-fra.com

Comment utiliser Django OAuth Toolkit avec Python Social Auth?

Je construis une API en utilisant Django Rest Framework. Plus tard, cette API est supposée être consommée par les appareils iOS et Android. Je souhaite permettre à mes utilisateurs de s'inscrire auprès de oauth2-providers tels que Facebook et Google. Dans ce cas, ils ne devraient pas avoir à créer de compte avec ma plateforme. Mais les utilisateurs devraient également pouvoir s'inscrire sans avoir un compte Facebook/Google, pour lequel j'utilise Django-oauth-toolkit. J'ai donc mon propre fournisseur oauth2.

Pour les fournisseurs externes, j'utilise python-social-auth, qui fonctionne correctement et crée automatiquement les objets utilisateur.

Je souhaite que les clients s'authentifient à l'aide de jetons porteurs, ce qui convient parfaitement aux utilisateurs qui se sont abonnés à mon fournisseur (Django-oauth-toolkit fournit un schéma d'authentification et des classes d'autorisations pour Django REST Framework) .
Cependant, python-social-auth implémente uniquement l'authentification par session. Il n'existe donc aucun moyen simple de créer des requêtes d'API authentifiées pour le compte d'utilisateurs enregistrés par un fournisseur oauth2 externe.

Si j'utilise un access_token qui a été généré par Django-oauth-toolkit, faire une requête comme celle-ci fonctionne:

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

Cependant, ce qui suit ne fonctionne pas car il n'y a pas de schéma d'authentification correspondant pour Django REST Framework et AUTHENTICATION_BACKENDS fourni par python-social-auth ne fonctionne que pour l'authentification basée sur la session:

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

L'utilisation de l'API navigable fournie par Django REST Framework après l'authentification avec python-social-auth fonctionne parfaitement, seuls les appels d'API sans cookie de session ne fonctionnent pas.

Je me demande quelle est la meilleure approche pour résoudre ce problème. De la façon dont je le vois, j'ai essentiellement deux options:

R: Lorsqu'un utilisateur s'inscrit avec un fournisseur oauth2 externe (géré par python-social-auth), connectez-vous au processus pour créer un oauth2_provider.models.AccessToken et continuez à utiliser 'oauth2_provider.ext.rest_framework.OAuth2Authentication', en authentifiant également les utilisateurs enregistrés auprès d'un fournisseur externe. . Cette approche est suggérée ici: https://groups.google.com/d/msg/Django-rest-framework/ACKx1kY7kZM/YPWFA2DP9LwJ

B: Utilisez python-social-auth pour l'authentification de demande d'API. Je pouvais obtenir mes propres utilisateurs dans python-social-auth en écrivant un backend personnalisé et en utilisant register_by_access_token. Cependant, étant donné que les appels d'API ne peuvent pas utiliser les sessions Django, cela signifierait que je devrais écrire un schéma d'authentification pour Django Rest Framework qui utilise les données stockées par python-social-auth. Quelques conseils sur la façon de faire cela peuvent être trouvés ici:
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
Cependant, si je comprends bien, python-social-auth ne vérifie le jeton que lors de la connexion et s’appuie ensuite sur la session Django. Cela signifierait que je devrais trouver un moyen d'empêcher python-social-auth de faire l'intégralité du flux oauth2-flow pour chaque requête d'API sans état et plutôt de vérifier les données stockées dans la base de données, ce qui n'est pas vraiment optimisé pour les requêtes, car stocké en tant que JSON (je pourrais utiliser UserSocialAuth.objects.get (extra_data__contains =) cependant) .
Je devrais aussi vérifier les portées d’un jeton d’accès et les utiliser pour vérifier les autorisations, ce que Django-oauth-toolkit fait déjà (TokenHasScope, required_scopes etc.).

Pour le moment, je suis plutôt enclin à utiliser l'option A, car Django-oauth-toolkit offre une bonne intégration avec Django Rest Framework et me permet d'obtenir tout ce dont j'ai besoin. Le seul inconvénient est que je dois "injecter" les access_tokens récupérés par python-social-auth dans le modèle AccessToken de Django-oauth-toolkit, qui se sent mal, mais serait probablement de loin l'approche la plus simple.

Est-ce que quelqu'un s'y oppose ou a peut-être abordé le même problème d'une manière différente? Est-ce que quelque chose d’évident me manque et que ma vie est plus dure que nécessaire? Si quelqu'un a déjà intégré Django-oauth-toolkit avec des fournisseurs python-social-auth et externes oauth2, je serais très reconnaissant de certains conseils ou opinions.

47
jeverling

Je l'ai résolu en utilisant votre option A.

Ce que je fais, c'est enregistrer les utilisateurs qui utilisent un tiers pour s'inscrire à l'aide de leur jeton d'accès tiers.

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

De cette façon, je peux émettre une requête GET comme celle-ci:

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

Et register_by_access_token est appelé. request.backend.do_auth interrogera le fournisseur pour obtenir les informations sur l'utilisateur à partir du jeton et enregistrera par magie un compte d'utilisateur avec les informations ou connectera l'utilisateur s'il est déjà enregistré. 

Ensuite, je crée un jeton manuellement et le retourne en tant que JSON pour permettre au client d'interroger mon 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"

Je ne suis pas sûr de la façon dont j'ai généré le jeton. Est-ce une bonne pratique? Eh bien, en attendant, ça marche !!

9
Felix D.

Peut-être que Django-rest-framework-social-oauth2 est ce que vous cherchez. Ce paquet dépend de python-social-auth et Django-oauth-toolkit, que vous utilisez déjà. J'ai rapidement parcouru la documentation et elle semble mettre en œuvre exactement ce que vous essayez de faire.

5
Serrano

Je faisais React Native avec expo et Django avec le framework Django REST. Cet article a fini par être la façon dont j'ai résolu l'enregistrement (inscription) avec facebook https://medium.com/@gabriel_gamil/react-native-expo-Django-facebook-authentication-sign-in-83625c49da7

tldr; utilisez Django-rest-auth _ { https://Django-rest-auth.readthedocs.io/en/latest/index.html } _

utilisez Django-allauth https://Django-allauth.readthedocs.io/en/latest/

0
Harry Moreno