web-dev-qa-db-fra.com

Utilisation de Google OAuth2 avec Flask

Quelqu'un peut-il m'indiquer un exemple complet d'authentification avec des comptes Google à l'aide de OAuth2 et Flask, et not sur App Engine?

J'essaie de faire en sorte que les utilisateurs accèdent à Google Agenda, puis l'utilisent pour extraire des informations de l'agenda et les traiter plus avant. Je dois également stocker et actualiser ultérieurement les jetons OAuth2.

J'ai consulté la bibliothèque oauth2client de Google et je peux faire en sorte que la danse commence à récupérer le code d'autorisation, mais je suis un peu perdu de là. En regardant le terrain de jeu OAuth 2.0 de Google, je comprends que je dois demander le jeton d'actualisation et le jeton d'accès, mais les exemples fournis dans la bibliothèque ne concernent que App Engine et Django.

J'ai également essayé d'utiliser le module OAuth de Flask qui contient des références à OAuth2, mais je ne vois aucun moyen d'échanger le code d'autorisation ici.

Je pourrais probablement coder manuellement les requêtes, mais je préférerais de beaucoup utiliser ou adapter un module python existant qui facilite les requêtes, gère correctement les réponses possibles et peut-être même aide au stockage des jetons.

Une telle chose existe t elle?

53
emning

Une autre réponse mentionne Flask-Rauth , mais n'entre pas dans les détails pour savoir comment l'utiliser. Il y a quelques pièges spécifiques à Google, mais je l'ai finalement mis en œuvre et cela fonctionne bien. Je l’intègre avec Flask-Login pour pouvoir décorer mes vues avec du sucre utile tel que @login_required

Je voulais pouvoir prendre en charge plusieurs fournisseurs OAuth2. Une partie du code est donc générique et basée sur l'excellent article de Miguel Grinberg sur la prise en charge de OAuth2 avec Facebook et Twitter ici .

Tout d'abord, ajoutez vos informations d'authentification Google spécifiques à partir de Google dans la configuration de votre application:

GOOGLE_LOGIN_CLIENT_ID = "<your-id-ending-with>.apps.googleusercontent.com"
GOOGLE_LOGIN_CLIENT_SECRET = "<your-secret>"

OAUTH_CREDENTIALS={
        'google': {
            'id': GOOGLE_LOGIN_CLIENT_ID,
            'secret': GOOGLE_LOGIN_CLIENT_SECRET
        }
}

Et lorsque vous créez votre application (dans mon cas, le __init__.py du module):

app = Flask(__name__)
app.config.from_object('config')

Dans votre module d'application, créez auth.py:

from flask import url_for, current_app, redirect, request
from rauth import OAuth2Service

import json, urllib2

class OAuthSignIn(object):
    providers = None

    def __init__(self, provider_name):
        self.provider_name = provider_name
        credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
        self.consumer_id = credentials['id']
        self.consumer_secret = credentials['secret']

    def authorize(self):
        pass

    def callback(self):
        pass

    def get_callback_url(self):
        return url_for('oauth_callback', provider=self.provider_name,
                        _external=True)

    @classmethod
    def get_provider(self, provider_name):
        if self.providers is None:
            self.providers={}
            for provider_class in self.__subclasses__():
                provider = provider_class()
                self.providers[provider.provider_name] = provider
        return self.providers[provider_name]

class GoogleSignIn(OAuthSignIn):
    def __init__(self):
        super(GoogleSignIn, self).__init__('google')
        googleinfo = urllib2.urlopen('https://accounts.google.com/.well-known/openid-configuration')
        google_params = json.load(googleinfo)
        self.service = OAuth2Service(
                name='google',
                client_id=self.consumer_id,
                client_secret=self.consumer_secret,
                authorize_url=google_params.get('authorization_endpoint'),
                base_url=google_params.get('userinfo_endpoint'),
                access_token_url=google_params.get('token_endpoint')
        )

    def authorize(self):
        return redirect(self.service.get_authorize_url(
            scope='email',
            response_type='code',
            redirect_uri=self.get_callback_url())
            )

    def callback(self):
        if 'code' not in request.args:
            return None, None, None
        oauth_session = self.service.get_auth_session(
                data={'code': request.args['code'],
                      'grant_type': 'authorization_code',
                      'redirect_uri': self.get_callback_url()
                     },
                decoder = json.loads
        )
        me = oauth_session.get('').json()
        return (me['name'],
                me['email'])

Cela crée une classe OAuthSignIn générique qui peut être sous-classée. La sous-classe Google tire ses informations de la liste d'informations publiée par Google (au format JSON ici ). Ces informations sont sujettes à modification. Cette approche garantit donc qu’elles sont toujours à jour. Une limitation de cela est que si une connexion Internet n'est pas disponible sur votre serveur au moment où l'application Flask est initialisée (le module importé), elle ne sera pas instanciée correctement. Cela ne devrait presque jamais être un problème, mais le stockage des dernières valeurs connues dans la base de données de configuration afin de couvrir cette éventualité est une bonne idée.

Enfin, la classe retourne un tuple de name, email dans la fonction callback(). Google renvoie en réalité beaucoup plus d'informations, y compris le profil Google+, s'il est disponible. Examinez le dictionnaire renvoyé par oauth_session.get('').json() pour tout voir . Si dans la fonction authorize() vous développez l'étendue (pour mon application, email est suffisant), vous pouvez accéder à encore plus d'informations via l'API Google.

Ensuite, écrivez le views pour lier le tout:

from flask.ext.login import login_user, logout_user, current_user, login_required

@app.route('/authorize/<provider>')
def oauth_authorize(provider):
    # Flask-Login function
    if not current_user.is_anonymous():
        return redirect(url_for('index'))
    oauth = OAuthSignIn.get_provider(provider)
    return oauth.authorize()

@app.route('/callback/<provider>')
def oauth_callback(provider):
    if not current_user.is_anonymous():
        return redirect(url_for('index'))
    oauth = OAuthSignIn.get_provider(provider)
    username, email = oauth.callback()
    if email is None:
        # I need a valid email address for my user identification
        flash('Authentication failed.')
        return redirect(url_for('index'))
    # Look if the user already exists
    user=User.query.filter_by(email=email).first()
    if not user:
        # Create the user. Try and use their name returned by Google,
        # but if it is not set, split the email address at the @.
        nickname = username
        if nickname is None or nickname == "":
            nickname = email.split('@')[0]

        # We can do more work here to ensure a unique nickname, if you 
        # require that.
        user=User(nickname=nickname, email=email)
        db.session.add(user)
        db.session.commit()
    # Log in the user, by default remembering them for their next visit
    # unless they log out.
    login_user(user, remember=True)
    return redirect(url_for('index'))

Enfin, ma vue /login et mon modèle pour que tout se produise:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if g.user is not None and g.user.is_authenticated():
        return redirect(url_for('index'))
    return render_template('login.html',
                           title='Sign In')

login.html:

{% extends "base.html" %}

{% block content %}

    <div id="sign-in">
        <h1>Sign In</h1>
        <p>
        <a href={{ url_for('oauth_authorize', provider='google') }}><img src="{{ url_for('static', filename='img/sign-in-with-google.png') }}" /></a>
    </div>
{% endblock %}

Assurez-vous que les adresses de rappel correctes sont enregistrées auprès de Google. L'utilisateur doit simplement cliquer sur "Connexion avec Google" sur votre page de connexion. Celui-ci les enregistrera et les ouvrira une session.

37
Aaron D

J'ai cherché pas mal de choses sur l'utilisation de différentes bibliothèques, mais toutes semblaient excessives dans un sens (vous pouvez les utiliser sur n'importe quelle plate-forme, mais vous avez besoin d'une tonne de code) ou la documentation ne m'a pas expliqué ce que je voulais. Longue histoire courte - je l'ai écrit à partir de zéro, donc comprendre le processus d'authentification vrai Google API. Ce n'est pas aussi difficile que ça en a l'air. En gros, vous devez suivre les instructions https://developers.google.com/accounts/docs/OAuth2WebServer et le tour est joué. Pour cela, vous devrez également vous inscrire à l'adresse https://code.google.com/apis/console/ pour générer les informations d'identification et enregistrer vos liens. J'ai utilisé un simple sous-domaine pointant vers l'adresse IP de mon bureau, car il n'autorise que les domaines.

Pour la connexion/la gestion des utilisateurs et les sessions, j'ai utilisé ce plugin pour flask http://packages.python.org/Flask-Login/ - il y aura du code basé sur cela.

Alors première chose à faire - vue d'index:

from flask import render_template
from flask.ext.login import current_user
from flask.views import MethodView

from myapp import app


class Index(MethodView):
    def get(self):
        # check if user is logged in
        if not current_user.is_authenticated():
            return app.login_manager.unauthorized()

        return render_template('index.html')

cette vue ne s'ouvrira donc pas tant que nous n'aurons pas authentifié l'utilisateur . À propos d'utilisateurs - modèle d'utilisateur:

from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy import Column, Integer, DateTime, Boolean, String

from flask.ext.login import UserMixin
from myapp.metadata import Session, Base


class User(Base):
    __table= 'myapp_users'

    id = Column(Integer, primary_key=True)
    email = Column(String(80), unique=True, nullable=False)
    username = Column(String(80), unique=True, nullable=False)

    def __init__(self, email, username):
        self.email = email
        self.username = username

    def __repr__(self):
        return "<User('%d', '%s', '%s')>" \
                % (self.id, self.username, self.email)

    @classmethod
    def get_or_create(cls, data):
        """
        data contains:
            {u'family_name': u'Surname',
            u'name': u'Name Surname',
            u'picture': u'https://link.to.photo',
            u'locale': u'en',
            u'gender': u'male',
            u'email': u'[email protected]',
            u'birthday': u'0000-08-17',
            u'link': u'https://plus.google.com/id',
            u'given_name': u'Name',
            u'id': u'Google ID',
            u'verified_email': True}
        """
        try:
            #.one() ensures that there would be just one user with that email.
            # Although database should prevent that from happening -
            # lets make it buletproof
            user = Session.query(cls).filter_by(email=data['email']).one()
        except NoResultFound:
            user = cls(
                    email=data['email'],
                    username=data['given_name'],
                )
            Session.add(user)
            Session.commit()
        return user

    def is_active(self):
        return True

    def is_authenticated(self):
        """
        Returns `True`. User is always authenticated. Herp Derp.
        """
        return True

    def is_anonymous(self):
        """
        Returns `False`. There are no Anonymous here.
        """
        return False

    def get_id(self):
        """
        Assuming that the user object has an `id` attribute, this will take
        that and convert it to `unicode`.
        """
        try:
            return unicode(self.id)
        except AttributeError:
            raise NotImplementedError("No `id` attribute - override get_id")

    def __eq__(self, other):
        """
        Checks the equality of two `UserMixin` objects using `get_id`.
        """
        if isinstance(other, UserMixin):
            return self.get_id() == other.get_id()
        return NotImplemented

    def __ne__(self, other):
        """
        Checks the inequality of two `UserMixin` objects using `get_id`.
        """
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

Il y a probablement quelque chose qui ne va pas avec UserMixin, mais je traiterai de ce dernier. Votre modèle utilisateur aura un aspect différent, il suffit de le rendre compatible avec flask-login.

Alors, que reste-t-il - l'authentification elle-même. J'ai défini pour flask-login cette vue de connexion est 'login'. La vue Login affiche le code HTML avec le bouton de connexion pointant vers Google - les redirections Google vers la vue Auth. Il devrait être possible de rediriger l'utilisateur vers Google au cas où il s'agirait d'un site Web réservé aux utilisateurs connectés.

import logging
import urllib
import urllib2
import json

from flask import render_template, url_for, request, redirect
from flask.views import MethodView
from flask.ext.login import login_user

from myapp import settings
from myapp.models import User


logger = logging.getLogger(__name__)


class Login(BaseViewMixin):
    def get(self):
        logger.debug('GET: %s' % request.args)
        params = {
            'response_type': 'code',
            'client_id': settings.GOOGLE_API_CLIENT_ID,
            'redirect_uri': url_for('auth', _external=True),
            'scope': settings.GOOGLE_API_SCOPE,
            'state': request.args.get('next'),
        }
        logger.debug('Login Params: %s' % params)
        url = settings.GOOGLE_OAUTH2_URL + 'auth?' + urllib.urlencode(params)

        context = {'login_url': url}
        return render_template('login.html', **context)


class Auth(MethodView):
    def _get_token(self):
        params = {
            'code': request.args.get('code'),
            'client_id': settings.GOOGLE_API_CLIENT_ID,
            'client_secret': settings.GOOGLE_API_CLIENT_SECRET,
            'redirect_uri': url_for('auth', _external=True),
            'grant_type': 'authorization_code',
        }
        payload = urllib.urlencode(params)
        url = settings.GOOGLE_OAUTH2_URL + 'token'

        req = urllib2.Request(url, payload)  # must be POST

        return json.loads(urllib2.urlopen(req).read())

    def _get_data(self, response):
        params = {
            'access_token': response['access_token'],
        }
        payload = urllib.urlencode(params)
        url = settings.GOOGLE_API_URL + 'userinfo?' + payload

        req = urllib2.Request(url)  # must be GET

        return json.loads(urllib2.urlopen(req).read())

    def get(self):
        logger.debug('GET: %s' % request.args)

        response = self._get_token()
        logger.debug('Google Response: %s' % response)

        data = self._get_data(response)
        logger.debug('Google Data: %s' % data)

        user = User.get_or_create(data)
        login_user(user)
        logger.debug('User Login: %s' % user)
        return redirect(request.args.get('state') or url_for('index'))

Donc tout est divisé en deux parties - une pour obtenir le token Google dans _get_token. Autre pour l’utiliser et récupérer les données utilisateur de base dans _get_data.

Mon fichier de paramètres contient:

GOOGLE_API_CLIENT_ID = 'myid.apps.googleusercontent.com'
GOOGLE_API_CLIENT_SECRET = 'my secret code'
GOOGLE_API_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'
GOOGLE_OAUTH2_URL = 'https://accounts.google.com/o/oauth2/'
GOOGLE_API_URL = 'https://www.googleapis.com/oauth2/v1/'

N'oubliez pas que les vues doivent avoir un chemin d'accès URL associé à l'application. Par conséquent, j'ai utilisé ce fichier urls.py pour pouvoir suivre plus facilement mes vues et importer moins d'éléments dans le fichier de création d'application de la fiole:

from myapp import app
from myapp.views.auth import Login, Auth
from myapp.views.index import Index


urls = {
    '/login/': Login.as_view('login'),
    '/auth/': Auth.as_view('auth'),
    '/': Index.as_view('index'),
}

for url, view in urls.iteritems():
    app.add_url_rule(url, view_func=view)

Tous ces éléments réunis permettent d'utiliser l'autorisation Google dans Flask. Si vous copiez le coller, cela nécessitera peut-être quelques réparations avec la documentation de flask-login et les mappages SQLAlchemy, mais l’idée est là.

32
JackLeo

Donnez Authomatic a try (je suis le mainteneur de ce projet). Il est très simple à utiliser, fonctionne avec tout framework Python et supporte 16 OAuth 2.0, 10 OAuth 1.0a fournisseurs et OpenID.

Voici un exemple simple expliquant comment authentifier un utilisateur auprès de Google et obtenir sa liste de vidéos YouTube:

# main.py

from flask import Flask, request, make_response, render_template
from authomatic.adapters import WerkzeugAdapter
from authomatic import Authomatic
from authomatic.providers import oauth2


CONFIG = {
    'google': {
        'class_': oauth2.Google,
        'consumer_key': '########################',
        'consumer_secret': '########################',
        'scope': oauth2.Google.user_info_scope + ['https://gdata.youtube.com'],
    },
}

app = Flask(__name__)
authomatic = Authomatic(CONFIG, 'random secret string for session signing')


@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
def login(provider_name):
    response = make_response()

    # Authenticate the user
    result = authomatic.login(WerkzeugAdapter(request, response), provider_name)

    if result:
        videos = []
        if result.user:
            # Get user info
            result.user.update()

            # Talk to Google YouTube API
            if result.user.credentials:
                response = result.provider.access('https://gdata.youtube.com/'
                    'feeds/api/users/default/playlists?alt=json')
                if response.status == 200:
                    videos = response.data.get('feed', {}).get('entry', [])

        return render_template(user_name=result.user.name,
                               user_email=result.user.email,
                               user_id=result.user.id,
                               youtube_videos=videos)
    return response


if __== '__main__':
    app.run(debug=True)

Il existe également un didacticiel Flask très simple qui explique comment authentifier un utilisateur via Facebook et Twitter et communiquer avec ses API pour lire les flux de nouvelles de ces utilisateurs.

19
Peter Hudec

Flask-Dance est une nouvelle bibliothèque qui relie Flask, Requests et OAuthlib. Il a une belle API et un support intégré pour Google auth, avec un rapide pour démarrer . Essaie!

5
singingwolfboy

Flask-oauth est probablement votre meilleur choix en ce moment pour un moyen spécifique de le faire. Autant que je sache, il ne prend pas en charge le rafraîchissement des jetons, mais cela fonctionnera avec Facebook. Nous l'utilisons pour cela et c'est bien vous n'avez pas besoin d'être spécifique au flacon, vous pouvez regarder demandes-oauth 

1
bluemoon

On dirait que le nouveau module Flask-Rauth est la réponse à cette question:

Flask-Rauth est une extension Flask qui vous permet d'interagir facilement avec les applications activées OAuth 2.0, OAuth 1.0a et Ofly. [...] Cela signifie que Flask-Rauth permettra aux utilisateurs de votre site Web Flask de se connecter à des services Web externes (c'est-à-dire l'API Twitter, l'API Graph Facebook, GitHub, etc.).

Voir: Flask-Rauth

1
emning

Pas spécifiquement pour google - https://github.com/lepture/flask-oauthlib et il contient un exemple de mise en œuvre du client et du serveur sur https://github.com/lepture/example -oauth2-server

0
Andrei Sura

Comme oauth2client est maintenant obsolète, je recommande ce que suggère bluemoon. Le modèle de Bruno Rocha de l'authentification Google OAuth2 dans Flask est un bon point de départ pour utiliser le robuste Flask-OAuthlib (installable à la pipette). Je recommande de mimer, puis d'agrandir pour répondre à vos besoins.

0
user2901351