Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask_dance: Cannot get OAuth token without an associated user

I want to migrate flask_dance with my application to make the user authorize using google and another social networks.

I am getting this error:

Cannot get OAuth token without an associated user

Before i do the connection between the blueprint and sqlalchemy backend, the application worked just fine, if i removed the google_blueprint.backend line the error disappear.

Here is my __init__.py:

import os
from flask import Flask, redirect, url_for, current_app
from flask_login import current_user
from develop.models import (
    db,
    User,
    OAuth
)

from flask_dance.contrib.google import make_google_blueprint
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend
from flask_dance.consumer import oauth_authorized
from sqlalchemy.orm.exc import NoResultFound

def create_app(config_object):
    app = Flask(__name__)
    app.config.from_object(config_object)
    db.init_app(app)
    login_manager.init_app(app)

    google_blueprint = make_google_blueprint(
        client_id=app.config['GOOGLE_CLIENT_ID'],
        client_secret=app.config['GOOGLE_CLIENT_SECRET'],
        scope=["profile", "email"]
    )
    app.register_blueprint(google_blueprint, url_prefix='/login')
    @oauth_authorized.connect_via(google_blueprint)
    def google_logged_in(blueprint, token):
        resp = blueprint.session.get("/oauth2/v2/userinfo")
        if resp.ok:
            account_info_json = resp.json()
            email = account_info_json['email']
            query = User.query.filter_by(email=email)
            try:
                user = query.one()
            except NoResultFound:
                user = User()
                user.image = account_info_json['picture']
                user.fullname = account_info_json['name']
                user.username = account_info_json['given_name']
                user.email = account_info_json['email']
                db.session.add(user)
                db.session.commit()
                login_user(get_user, remember=True)

                identity_changed.send(
                    current_app._get_current_object(),
                    identity=Identity(get_user.id)
                )

    @login_manager.user_loader
    def load_user(userid):
        return User.query.get(userid)

    google_blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user)

    return app

Here is also my tables how i organized them in models.py:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer(), primary_key=True)
    image = db.Column(db.String(), nullable=True)
    fullname = db.Column(db.String())
    username = db.Column(db.String(), unique=True)
    password = db.Column(db.String())
    email = db.Column(db.String(), unique=True)

class OAuth(OAuthConsumerMixin, db.Model):
    user_id = db.Column(db.Integer(), db.ForeignKey(User.id))
    user = db.relationship(User)

Please any help would be appreciated :)

like image 938
swordfish Avatar asked Dec 04 '17 23:12

swordfish


1 Answers

TL;DR: You can disable this exception by setting user_required=False on the SQLAlchemyStorage object. However, the exception is being raised for a reason, and if you simply disable it like this, your database may get into an unexpected state where some OAuth tokens are not linked to users. There's a better way to solve this problem. Read on for details.


I am the author of Flask-Dance. This Cannot get OAuth token without an associated user exception is only present in version 0.13.0 and above of Flask-Dance. (CHANGELOG is here.) The pull request introducing this change has some more context for why the change was made.

There are several different ways to use OAuth. Here are some example use cases, all of which Flask-Dance supports:

  1. I want to build a bot that can connect to one specific service, such as a Twitter bot that tweets to a specific account, or a Slack bot that connects to a specific Slack team. I want this bot to respond to HTTP requests, so it has to run as a website, even though I don't expect people to actually use this website directly.
  2. I want to build a website where users can log in. Users need to create an account on my website using a username and password. After they have created an account, users may decide to link their account to other OAuth providers, like Google or Facebook, to unlock additional functionality.
  3. I want to build a website where users can log in. Users should be able to create their account simply by logging in with GitHub (or any other OAuth provider). Users should not need to create a new password for my website.

Use case 1 is the simplest: do not pass a user or user_id argument to your SQLAlchemyStorage, and it will assume that your application does not use multiple user accounts. This means that your website can only link to one particular account on the remote service: only one Twitter account, only one Slack team, etc.

Use case 2 is also pretty simple: pass a user or user_id argument to your SQLAlchemyStorage. Flask-Dance will save the OAuth token into your database automatically, and link it to the user that is currently logged in.

Use case 3 is more complex, since it involves automatically creating both the OAuth token and the local user account at the same time. Different applications have different requirements for creating user accounts, and there's no way for Flask-Dance to know what those requirements are. As a result, Flask-Dance cannot handle this use case automatically. You must hook into the oauth_authorized signal, create the user account and associate it with the OAuth token manually, and return False to tell Flask-Dance to not attempt to handle the OAuth token automatically.

Before version 0.13.0, it was possible to accidentally create OAuth tokens in the database that were not linked with any users at all. In use case 3, the OAuth token is created before a local user account exists for that user, so Flask-Dance would save the OAuth token to the database without any linked local user account. You could use the oauth_authorized handler to associate the OAuth token with a local user account afterwards, but if your code is buggy and raises an exception, then the OAuth token could remain in your database, forever unlinked to any users.

Starting in version 0.13.0, Flask-Dance detects this problem and raises an exception, instead of saving an OAuth token to your database without an associated local user account. There are two ways to resolve this problem:

  1. Rewrite your code to manually create the user account and associate it with the OAuth token. The documentation contains some example code you can use for this.
  2. Disable this check, and tell Flask-Dance that it's OK to create OAuth tokens without associated users. You can disable this check by setting user_required=False on the SQLAlchemyStorage object.

I believe that option 1 is the better solution by far, but it requires more understanding of what Flask-Dance is actually doing behind the scenes. I've written some documentation that describes how to handle multi-user setups, which discusses this problem as well.

like image 146
singingwolfboy Avatar answered Oct 06 '22 04:10

singingwolfboy