Code Monkey home page Code Monkey logo

flask-dance's Introduction

Flask-Dance Build status Test coverage Documentation

Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project could easily support OAuth providers in the future, as well. The full documentation for this project is hosted on ReadTheDocs, including the full list of supported OAuth providers, but this README will give you a taste of the features.

Installation

Just the basics:

$ pip install Flask-Dance

Or if you're planning on using the SQLAlchemy storage:

$ pip install Flask-Dance[sqla]

Quickstart

If you want your users to be able to log in to your app from any of the supported OAuth providers, you've got it easy. Here's an example using GitHub:

from flask import Flask, redirect, url_for
from flask_dance.contrib.github import make_github_blueprint, github

app = Flask(__name__)
app.secret_key = "supersekrit"
blueprint = make_github_blueprint(
    client_id="my-key-here",
    client_secret="my-secret-here",
)
app.register_blueprint(blueprint, url_prefix="/login")

@app.route("/")
def index():
    if not github.authorized:
        return redirect(url_for("github.login"))
    resp = github.get("/user")
    assert resp.ok
    return "You are @{login} on GitHub".format(login=resp.json()["login"])

If you're itching to try it out, check out the flask-dance-github example repository, with detailed instructions for how to run this code.

The github object is a context local, just like flask.request. That means that you can import it in any Python file you want, and use it in the context of an incoming HTTP request. If you've split your Flask app up into multiple different files, feel free to import this object in any of your files, and use it just like you would use the requests module.

You can also use Flask-Dance with any OAuth provider you'd like, not just the pre-set configurations. See the documentation for how to use other OAuth providers.

Storages

By default, OAuth access tokens are stored in Flask's session object. This means that if the user ever clears their browser cookies, they will have to go through the OAuth dance again, which is not good. You're better off storing access tokens in a database or some other persistent store, and Flask-Dance has support for swapping out the token storage. For example, if you're using SQLAlchemy, set it up like this:

from flask_sqlalchemy import SQLAlchemy
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin, SQLAlchemyStorage

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    # ... other columns as needed

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

# get_current_user() is a function that returns the current logged in user
blueprint.storage = SQLAlchemyStorage(OAuth, db.session, user=get_current_user)

The SQLAlchemy storage seamlessly integrates with Flask-SQLAlchemy, as well as Flask-Login for user management, and Flask-Caching for caching.

Full Documentation

This README provides just a taste of what Flask-Dance is capable of. To see more, read the documentation on ReadTheDocs.

Security contact information

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

flask-dance's People

Contributors

adamgold avatar atwalsh avatar bachmann1234 avatar charlesreid1 avatar chuyachia avatar daenney avatar eblume avatar fenhl avatar gchq83514 avatar heman avatar jgeorgeson avatar jonathanhuot avatar martinffx avatar michaeldel avatar mikeabrahamsen avatar ml-evs avatar mvexel avatar nathanwailes avatar nerrixde avatar nickdirienzo avatar owencompher avatar przemoo16 avatar r8 avatar rooterkyberian avatar seekheart avatar singingwolfboy avatar skion avatar steven-martins avatar tadaboody avatar tcmryan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-dance's Issues

Fails to authenticate OAuth2 when blinker lib isn't installed

127.0.0.1 - - [09/Mar/2015 14:34:49] "GET /login/google/authorized?state=<redacted>&code=<redacted> HTTP/1.1" 500 -
Traceback (most recent call last):
  File "<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File ""<path to my app>/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File ""<path to my app>/lib/python2.7/site-packages/flask_dance/consumer/oauth2.py", line 203, in authorized
    if not any(ret == False for func, ret in results):
TypeError: 'NoneType' object is not iterable

KeyError: 'google_oauth_state'

Hello,

I receive the error KeyError: 'google_oauth_state' in case the browser back button is hit after successful authentication (at least in the google authentication example provided)

How to reproduce

Take this script which basically resembles the google authentication example

from flask import Flask, redirect, url_for
from flask_dance.contrib.google import make_google_blueprint, google

app = Flask(__name__)
app.secret_key = "supersekrit"
app.debug = True
import os
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

blueprint = make_google_blueprint(
    client_id="my-key-here",
    client_secret="my-secret-here",
    scope=["profile", "email"]
)
app.register_blueprint(blueprint, url_prefix="/login")

@app.route("/")
def index():
    if not google.authorized:
        return redirect(url_for("google.login"))
    resp = google.get("https://www.googleapis.com/oauth2/v1/userinfo")
    assert resp.ok, resp.text
    return resp.text

if __name__ == "__main__":
    app.run()

(Note that I set the OAUTHLIB_INSECURE_TRANSPORT environment variable as described in the docs but then in python code. Furthermore I used a different info url and just dump the whole response text. But I don't think that is relevant here)

The point is, if you start the app and navigate to it in the browser, You are sent to the google login screen. After that, you will be redirected to the appropriate blueprint url which then points you back to the index route. Fetching the userinfo is no problem either.
But when you hit the back button in your browser, the KeyError occurs which originates at flask_dance/consumer/oauth2.py on line 196.

Issues with examples

Hello, so I've installed this wonderful extension, and got it to work, with a bit of a hack (more on this later). What I found - the examples in the documentation are a bit of.

@app.route("/")
def index():
    if not azure.authorized:
        return redirect(url_for("azure.login"))
    resp = azure.get("/v1.0/me")
    assert resp.ok
    return "You are {mail} on Azure AD".format(mail=resp.json()["mail"])

So this code doesnt reach code after resp = azure.get("/v1.0/me")? Is this expected? If it is, why this code is even there? But this code runs if you go to the same page again.

So I have 2 questions:

  1. How to setup session variable with the username? (I've done this with a super hack)
  2. Why do you have assert resp.ok in the code if it doesn't get reached?

As for my hack, I'm redirecting back to login page after successful auth, to update session with a property containing username. that's pretty hackish, what's the proper way to do that?
Thanks!

Get Webhook info for Slack

Hi. As per slacks documention I should get some slack webhook info on auth. Like:

{
"access_token": "xoxp-XXXXXXXX-XXXXXXXX-XXXXX",
"scope": "incoming-webhook,commands,bot",
"team_name": "Team Installing Your Hook",
"team_id": "XXXXXXXXXX",
"incoming_webhook": {
    "url": "https://hooks.slack.com/TXXXXX/BXXXXX/XXXXXXXXXX",
    "channel": "#channel-it-will-post-to",
    "configuration_url": "https://teamname.slack.com/services/BXXXXX"
},
"bot":{
    "bot_user_id":"UTTTTTTTTTTR",
    "bot_access_token":"xoxb-XXXXXXXXXXXX-TTTTTTTTTTTTTT"
    }
}

However, I don't see the 'incoming_webhook' information. I see the bot key and everything else. Any idea why?

Thanks.

QuickStart/documentation doesn't say what "github.get('/user')" DOES

The QuickStart shows calling github.get('/user'), but it doesn't say what that line is intended to do (get the user resource). It might also be good for the example to include de-json-ifying the response body and get something useful from it, e.g. display the user's username.

working outside of application context

I'm fairly new to Flask, and am using Github authentication. I'm running into an issue when I try to access the Github object outside of the controller. I want to use the api to create an object in-memory dictionary so I can easily access repo and branch info. You can see a bit of example code here https://gist.github.com/citizenken/f29f639de44d20e97bb4.

When I do a github.authenticated check in a Flask route in controller.py, github instantiates properly. When I try to access github.authenticated in lib.py as the app starts up, github instantiates as <LocalProxy unbound> and I get the following stack trace:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 2358, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1778, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/repo/launch_my_package.py", line 2, in <module>
    from my_package import app
  File "/repo/my_package/__init__.py", line 11, in <module>
    import controller
  File "/repo/my_package/controller.py", line 7, in <module>
    from lib import gitservice
  File "/repo/my_package/lib.py", line 36, in <module>
    gitservice = GitService()
  File "/repo/my_package/lib.py", line 22, in __init__
    self.get_refs()
  File "/repo/my_package/lib.py", line 26, in get_refs
    if github.authorized:
  File "/repo/my_package_venv/lib/python2.7/site-packages/werkzeug/local.py", line 342, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/repo/my_package_venv/lib/python2.7/site-packages/werkzeug/local.py", line 301, in _get_current_object
    return self.__local()
  File "/repo/my_package_venv/lib/python2.7/site-packages/flask/globals.py", line 27, in _lookup_app_object
    raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context

What exactly is happening here? I have a token stored in the DB, and I am able to interact with the API inside the controller, so I don't think authorization is the problem.

Any help would be great!

Is it possible to use an application factory

Is it possible to use an application factory without @oauth_authorized.connect_via being part of create_app? I'm probably missing something, but I'm not seeing a way and I really don't want to have auth login as part of create_app.

sqla-multiuser.rst broken by Flask-Login 0.3.0 changes

The home.html template produces the following error:

builtins.AttributeError
AttributeError: 'bool' object has no attribute 'call'

Cause: "Since 0.3.0, Flask-Login has set is_active is_authenticated is_anonymous to property in UserMixin, so we should use current_user.is_authenticated instead of current_user.is_authenticated()." See miguelgrinberg/flasky#74.

Missing required parameter: refresh_token

I have a custom backend. All works fine, but i have problem when token was expired...
What about refresh token? I didnt find anything about this problem...

Custom backend:

class MongoBackend(BaseBackend):
    def get(self, blueprint):
        from ..collections import users_collection
        try:
            user = users_collection.find_one({'_id': ObjectId(current_user.id), 'oauth.provider': 'google'})
            return user['oauth']['token']
        except:
            return None

    def set(self, blueprint, token):
        from ..collections import users_collection
        users_collection.update_one({'_id': ObjectId(current_user.id)}, {'$set': {'oauth.token': token}})

    def delete(self, blueprint):
        from ..collections import users_collection
        users_collection.update_one({'_id': ObjectId(current_user.id), 'oauth': {'$set': {'token': ''}}})  # i know that didnt work
        return None

User scheme:

{
    "_id" : ObjectId("5a3d489ecefa5b3ad0b77b46"),
    "email" : "[email protected]",
    "oauth" : {
        "provider" : "google",
        "token" : {
            "access_token" : "bla bla bla",
            "expires_in" : 3420,
            "id_token" : "bla bla bla",
            "token_type" : "Bearer",
            "expires_at" : 1510969326.18578
        }
    }
}

When token was expired i see:
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError: (invalid_request) Missing required parameter: refresh_token

No documentation on allowed redirect URIs

Some OAuth providers (Google, for example) require you to set explicit allowed redirect URIs. It is non-obvious from existing documentation what URI flask-dance requires be set there. It should be set to http://:hostname/:flask-dance-url-prefix/:provider/authorized, e.g. for Google OAuth http://localhost:5000/login/google/authorized

StrictVersion check fails with Flask==0.11.dev0

  File "/home/.../.venv/lib64/python3.5/site-packages/flask_dance/consumer/base.py", line 37, in __init__
    if StrictVersion(flask.__version__) < StrictVersion('1.0'):
  File "/usr/lib64/python3.5/distutils/version.py", line 40, in __init__
    self.parse(vstring)
  File "/usr/lib64/python3.5/distutils/version.py", line 137, in parse
    raise ValueError("invalid version number '%s'" % vstring)
ValueError: invalid version number '0.11.dev0'

Question regrading Custom Backends

I'm writing a custom backend that uses pymongo to find the current user.
Question is from the documentation I'm not sure how the custom backend would know what the username I'm passing in is? Is that where the blueprint argument comes in to play or?

for reference this snippet from docs:

import os
import os.path
import json
from flask_dance.consumer.backend import BaseBackend

class FileBackend(BaseBackend):
    def __init__(self, filepath):
        super(FileBackend, self).__init__()
        self.filepath = filepath

    def get(self, blueprint):
        if not os.path.exists(self.filepath):
            return None
        with open(self.filepath) as f:
            return json.load(f)

    def set(self, blueprint, token):
        with open(self.filepath, "w") as f:
            json.dump(token, f)

    def delete(self, blueprint):
        os.remove(self.filepath)

Where does the user info live?

Security considerations

Hey, I am currently digging in the Python OAuth area and wanted to share some insights / security considerations.

  1. Flask Session is not the right place to store access_tokens as it is only Base64 encoded. "As per the RFC, "[tokens] MUST be kept confidential in transit and storage."
    A secure alternative is the use of server side sessions with Flask Session (the addon) or as it is promoted in the docs - storing it in a database on the server side.
    --> In my opinion this should be clarified on the front page - the default option == cookies is not secure
    See also http://security.stackexchange.com/questions/115695/when-should-server-side-sessions-be-used-instead-of-client-side-sessions/115714#115714
  2. From glancing over the code I wasn't sure but I assume Flask Dance uses the state parameter internally? Or is it up to the developer using Flask Dance to make sure the state parameter is used?

offline tokens still do not work

Recently there were some commits to allow offline tokens to be requested, but the correct URL parameter is not passed to the underlying libraries, so no refresh tokens are returned. The basic snippet asking for offline access is:

google_blueprint = make_google_blueprint(client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
                    offline=True, # needed to get refresh tokens! flask 0.4.3 needed
                    scope=YOUTUBE_SCOPES,
                    redirect_url="/oauth2callback",
                    login_url="/google",
                    authorized_url="/catch")

app.register_blueprint(google_blueprint)

which produces

DEBUG:requests_oauthlib.oauth2_session:Requesting url https://accounts.google.com/o/oauth2/token using method POST.
DEBUG:requests_oauthlib.oauth2_session:Supplying headers {u'Content-Type': u'application/x-www-form-urlencoded;charset=UTF-8', u'Accept': u'application/json'} and data {u'code': u'foobar', u'client_secret': u'bar', u'grant_type': u'authorization_code', u'client_id': u'foo', u'redirect_uri': u'http://foo.com/catch'}
DEBUG:requests_oauthlib.oauth2_session:Passing through key word arguments {'verify': True, 'json': None, 'timeout': None, 'auth': None}.
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): accounts.google.com
DEBUG:requests.packages.urllib3.connectionpool:"POST /o/oauth2/token HTTP/1.1" 200 None
DEBUG:requests_oauthlib.oauth2_session:Prepared fetch token request body grant_type=authorization_code&code=foobar&redirect_uri=http%3A%2F%2Ffoo.com%2Fcatch&client_id=foo&client_secret=bar
DEBUG:requests_oauthlib.oauth2_session:Request to fetch token completed with status 200.
DEBUG:requests_oauthlib.oauth2_session:Request headers were {'Content-Length': '277', 'Accept-Encoding': 'gzip, deflate', 'Accept': u'application/json', 'User-Agent': 'python-requests/2.5.3 CPython/2.7.6 Linux/3.13.0-45-generic', 'Connection': 'keep-alive', 'Content-Type': u'application/x-www-form-urlencoded;charset=UTF-8'}
DEBUG:requests_oauthlib.oauth2_session:Request body was code=foobar&client_secret=foo&grant_type=authorization_code&client_id=xxx
DEBUG:requests_oauthlib.oauth2_session:Response headers were {'alternate-protocol': '443:quic,p=0.5', 'x-xss-protection': '1; mode=block', 'x-content-type-options': 'nosniff', 'content-disposition': 'attachment; filename="json.txt"; filename*=UTF-8\'\'json.txt', 'transfer-encoding': 'chunked', 'accept-ranges': 'none', 'expires': 'Fri, 01 Jan 1990 00:00:00 GMT', 'vary': 'Accept-Encoding', 'server': 'GSE', 'pragma': 'no-cache', 'cache-control': 'no-cache, no-store, max-age=0, must-revalidate', 'date': 'Thu, 26 Mar 2015 22:52:06 GMT', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json; charset=utf-8'} and content {
  "access_token" : "xxx",
    "token_type" : "Bearer",
      "expires_in" : 3600
      }.

Notice that an oauth token is returned, but it does not have a refresh_token because the offline=True did not propagate.

This is using Flask-Dance 0.4.3 on Python 2.7.6

GitLab OAuth providor, client MissingTokenError before requesting token

I'm writing Flask app to drive some GitLab workflows that aren't supported by their API. I don't really need to have a user database/profile in my app. My app just needs to fetch the list of GitLab groups that the user has access to, they select a group, and then my app will make API calls and splinter/selenium calls to perform the workflow. I've configured my application in GitLab per this doc

https://docs.gitlab.com/ee/integration/oauth_provider.html#oauth-applications-in-the-admin-area

I've setup a OAuth2ConsumerBlueprint object with the client ID/secret and URLs for authorization and tokens, according to this doc

https://docs.gitlab.com/ee/api/oauth2.html

When I click on the <a href={{url_for("oauth-gitlab.login")}}>Login with GitLab</a> link in my app, it takes me to my GitLab instance to login and asks me to approve, then it redirects me back to my app and that throws oauthlib.oauth2.rfc6749.errors.MissingTokenError.

File "/home/jgeorgeson/Code/Dev-Engineering/gitlab-import-wrapper/import-wrapper/lib/python3.6/site-packages/flask_dance/consumer/oauth2.py", line 234, in authorized
**self.token_url_params
File "/home/jgeorgeson/Code/Dev-Engineering/gitlab-import-wrapper/import-wrapper/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py", line 244, in fetch_token
self._client.parse_request_body_response(r.text, scope=self.scope)
File "/home/jgeorgeson/Code/Dev-Engineering/gitlab-import-wrapper/import-wrapper/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 408, in parse_request_body_response
self.token = parse_token_response(body, scope=scope)
File "/home/jgeorgeson/Code/Dev-Engineering/gitlab-import-wrapper/import-wrapper/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 379, in parse_token_response
validate_token_parameters(params)
File "/home/jgeorgeson/Code/Dev-Engineering/gitlab-import-wrapper/import-wrapper/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 389, in validate_token_parameters
raise MissingTokenError(description="Missing access token parameter.")

Based on the 2nd gitlab.com link it seems like flask should be making a call to the token URL using the code returned by the authorization URL, but is instead expecting the token to be in the response already.

detect https not the right way

I've just scanned the code, and found

if request.headers.get("X-Forwarded-Proto", "http") == "https":
    url = url.with_scheme("https")

It is not the right way. Application code should not take care of such thing, it should always read from wsgi.url_scheme.

wsgi.url_scheme can be rewrite by gunicorn/uwsgi etc.

For gunicorn: http://docs.gunicorn.org/en/0.16.0/configure.html#secure-scheme-headers

For uwsgi: http://uwsgi-docs.readthedocs.org/en/latest/Vars.html#uwsgi-scheme

Wrap known oauthlib errors with an intelligible message

Two common uses of flask-dance require environment variables to be set for oauthlib to work.

  • OAUTHLIB_RELAX_TOKEN_SCOPE=1: allow changing scopes (needed for google?)
  • OAUTHLIB_INSECURE_TRANSPORT=1: allow redirecting back to a non-https site

By catching these errors and raising something with a more informative message, users could avoid having to dive into flask-dance code to figure out what's necessary to fix these.

OAuth2 login() broken

bp = OAuth2ConsumerBlueprint(
    'cern_auth',
    __name__,
    url_prefix='/oauth',
    # oauth specific settings
    client_id='***',
    client_secret='***',
    token_url='https://oauth.web.cern.ch/OAuth/Token',
    authorize_url='https://oauth.web.cern.ch/OAuth/Authorize',
    authorized_url='/cern'
)

When going to the login url I get this exception. I've also tried older oauthlib versions (since no specific version is required), but I got the same error.

  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/flask_dance/consumer/oauth2.py", line 172, in login
    log.debug("client_id = %s", self.client_id)
  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/flask_dance/consumer/oauth2.py", line 139, in client_id
    return self.session.client_id
  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/lazy/lazy.py", line 28, in __get__
    value = self.__func(inst)
  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/flask_dance/consumer/oauth2.py", line 158, in session
    **self.kwargs
  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/flask_dance/consumer/requests.py", line 65, in __init__
    super(OAuth2Session, self).__init__(*args, **kwargs)
  File "/home/adrian/dev/.../.venv/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 66, in __init__
    super(OAuth2Session, self).__init__(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'authorize_url'

Currently installed package versions:

oauthlib==1.1.1
requests==2.10.0
requests-oauthlib==0.6.1

Unmanaged access of declarative attribute __tablename__

When I start up my Flask app, I get the following warning:

/repo/package_venv/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py:173: 
SAWarning: Unmanaged access of declarative attribute __tablename__ 
from non-mapped class OAuthConsumerMixin(desc.fget.__name__, cls.__name__))

Looking at the mixin, it is because tablename is a function instead of a property of the mixin. You pass cls into that function to dynamically create the tablename. Is there a way to fix this?

Change GitHub scope based on user?

This is more a question than an issue but I did not find a more suited place to ask and did not want to bother @singingwolfboy via private E-Mail, so here we go:

I'm writing a Flask web app that utilizes flask-dance for GitHub authentication. 99,99% of all users will only need minimal information from GitHub and the default scope is defined that way: Minimal. But a few selected users will act as administrators and import additional information from GitHub (organization, repositories, quite a lot of information). Is it possible to use a different scope based on the user that is signing in (identified by the GitHub username)? I was thinking I might need to do the OAuth dance twice (first for identifying the user and to fetch their username), but I'm not sure this is a sane approach.

And thank you very much for creating this awesome flask extension - It's a lifesaver!

Does this work with rest-plus?

Wanted to ask if there are any examples or if flask-dance can work with flask-rest-plus?
I'm currently looking for an Oauth lib that can with google for a restful backend I have in the works.

"ValueError: Cannot get OAuth token without an associated user"

I am using ( https://gist.github.com/CT83/7881e27487fbcc31cc6c319d39212de3 ), I get "ValueError: Cannot get OAuth token without an associated user" on the webpage when I visit http://127.0.0.1:5000/twitter my error is similar to this (https://stackoverflow.com/questions/47643448/flask-dance-cannot-get-oauth-token-without-an-associated-user)๏ปฟ

Could someone respond to this please? I am sorry to be creating an Issue on this Repo but I couldn't find any help elsewhere.

PS I followed this YouTube Video

ImportError on sqla.py

When I try to use the OAuthConsumerMixin to save my OAuth credentials to my database, I'm getting an import error from the sqla.py file in the flask_dance.consumer.backend module. The specific error I'm getting is 'ImportError: No module named sqlalchemy_utils'. Do I need to install another package or is this a type in the code?

Example using a YT API after Google auth

I have been using flask-dance and it is pretty awesome! I am using it to connect to Google with various YouTube scopes and the oauth tokens get stored in my database with the proper foreign keys to my user table.

Currently I am trying to tie together flask-dance with one of these examples:

https://github.com/youtube/api-samples/tree/master/python

Would you mind giving some advice on using my Flask-Dance google blueprint to accomplish one of those examples? I can prepare it as a Pull Request for the docs, if you would like.

'OAuth1' object has no attribute 'signature_method'

Hey David,

I'm trying to use flask-dance (and eventually flask-login) to create a simple Twitter app to be deploy on Heroku. I am running into the error in the title.

The error with more context is:

   File "/Users/tony_chu/Documents/communal-blocklist/main.py", line 17, in index
     if not twitter.authorized:
   File "/Users/tony_chu/Documents/communal-blocklist/venv/lib/python2.7/site-packages/werkzeug/local.py", line 338, in __getattr__
     return getattr(self._get_current_object(), name)
   File "/Users/tony_chu/Documents/communal-blocklist/venv/lib/python2.7/site-packages/requests_oauthlib/oauth1_session.py", line 179, in authorized
     if self._client.signature_method == SIGNATURE_RSA:

My code looks like a lightly edited version of your Github demo. If there's something obvious that I'm screwing up here, I'm truly sorry. Please let me know what I'm missing.

import os
from flask import Flask, redirect, url_for
from flask_dance.contrib.twitter import make_twitter_blueprint, twitter

app = Flask(__name__)
app.config.from_pyfile('config.py')

# os.environ variable are either in .env (locally), or set via heroku config:set TWITTER_KEY=XXXXXXX
twitter_blueprint = make_twitter_blueprint(
    api_key=os.environ['TWITTER_KEY'],
    api_secret=os.environ['TWITTER_SECRET'],
    redirect_url="http://localhost:5000/",
)
app.register_blueprint(twitter_blueprint, url_prefix="/login")

@app.route("/")
def index():
    if not twitter.authorized:
        return redirect(url_for("twitter.login"))

    resp = twitter.get("/user")
    assert resp.ok
    return "You are @{login} on Twitter".format(login=resp.json()["login"])

if __name__ == "__main__":
    app.run()

In case it helps, here's my requirements.txt for my virtualenv

Flask==0.10.1
Flask-Dance==0.2.3
Flask-SQLAlchemy==2.0
Jinja2==2.7.3
MarkupSafe==0.23
SQLAlchemy==0.9.8
SQLAlchemy-Utils==0.27.0
URLObject==2.4.0
Werkzeug==0.9.6
gunicorn==19.1.1
itsdangerous==0.24
oauthlib==0.6.3
psycopg2==2.5.4
requests==2.4.3
requests-oauthlib==0.4.2
six==1.8.0
wsgiref==0.1.2

p.s. For extra, extra context, here's what I'm trying to do: http://blog.tonyhschu.ca/post/100476977371/communal-blocklist-experiment-day-1

How i can refresh my access_token manualy?

Man, your lib work fine, but i have a trouble :(
How do you refresh the token? I try search answer in doc, but didnt find..
For example: my user successfull logined with flask dance. In my DB a have a dict:

{
    "access_token" : "ya29.Gl...O8",
    "expires_in" : 3599,
    "id_token" : "eyJhb...Sf8zr5kAE-0HKf67UlgA",
    "token_type" : "Bearer",
    "expires_at" : 1510427464.66068,
    "refresh_token" : "1/ybsh5gTXrcdz...zdZT8AUD"
}

And autorefreshing works fine. But how it work? It's a magic?! Where you are updating token?

Now, i want to use gspread to work with Google Sheets. I made a custom credentials class and it's not a big problem. But it works only if my access_token not expired.

And how i can refresh my token with your lib or manualy? Thanks for answer and sorry for my bad English!

How does twitter.authorized know it's me?

I don't really understand how I can log out, delete my cookie, then click the "Sign in with Twitter" button and it somehow knows who I am and just logs me in directly. twitter.authorized is evaluating to True, and I saw that the code I think that's being evaluating to True is:

bool(self._client.client.client_secret) and
bool(self._client.client.resource_owner_key) and
bool(self._client.client.resource_owner_secret)

How can the session still have a resource_owner_key and resource_owner_secret after I've logged out, deleted my session cookie in Chrome, and restarted the server?

Twitter Oauth Dance using SQLa Backend failing in current version (0.13), was working in version 0.11.1

The attached code file sets up a Twitter app with a SQLAlchemy backend.
On installing Flask Dance from pip install today currently (version 0.13), the code doesn't works.
If I manually download the package files code as it was in the commit 2ac2536 on 01 Aug 2017 and replace the pip installed files, the code works as expected and the auth dance is completed successfuly.

The code raises an error ValueError ("Cannot delete OAuth token without an associated user") and is not affecting GitHub and Google setups but affects Twitter.
app txt

Consider bubbling oauth providers up

I can look into a PR for this if you like the idea....

So I have been playing with using flask-dance with facebook oauth (potential PR there as well)

Facebook has an... lets call it advanced... control panel for its apps. For a newbie it can be easy to make mistakes. Personally I found it difficult to debug these mistakes when using flask-dance.

So take redirect uri that's totally wrong! When I test my app I see this

screen shot 2015-05-03 at 10 35 03 pm

But thats absolutely useless to me. Dig into the source and there is a more useful error message lurking

screen shot 2015-05-03 at 10 44 57 pm

'{"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100}}'

Now I know to fix my facebook app settings

screen shot 2015-05-03 at 10 33 31 pm

Perhaps you can surface the response text when a request fails.

oauthlib.oauth2.rfc6749.errors.TokenExpiredError

When using the google auth example setup code, an oauthlib.oauth2.rfc6749.errors.TokenExpiredError is being thrown in case the access token received is expired which causes an internal server error to be shown.

I suppose that exception should be handled by flask-dance itself by catching it and in case of a missing refresh token as seen here, trashing the auth info and probably initiating re-authentication.
I did not test however what happens with forced approval prompt and offline access type (as further described here http://googlecode.blogspot.nl/2011/10/upcoming-changes-to-oauth-20-endpoint.html and on various other places of course..) which forces refresh token passing upon authentication.

How to reproduce

  1. create new project with a fresh set of credentials using the developer console of google with correct redirect urls (http://localhost:5000/login/google/authorized for example)

  2. add the credentials to the google auth example code
    My example code so far (with added logging and an adjustment to let it work over plain HTTP for the sake of this example and dumping the whole user info instead of just the email) is:

from flask import Flask, redirect, url_for
from flask_dance.contrib.google import make_google_blueprint, google

app = Flask(__name__)
app.secret_key = "supersekrit"
app.debug = True
import os
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

blueprint = make_google_blueprint(
    client_id="my-key-here",
    client_secret="my-secret-here",
    scope=["profile", "email"]
)
app.register_blueprint(blueprint, url_prefix="/login")

# Some logging setup
import logging
stream_handler = logging.StreamHandler()
formatter = logging.Formatter(
    "[%(asctime)s] [%(process)d] [%(levelname)s] [%(name)s]: "
    "%(pathname)s:%(lineno)d - "
    "%(message)s"
)
stream_handler.setFormatter(formatter)
app.logger.addHandler(stream_handler)
app.logger.info("app started")
logging.basicConfig(level=logging.DEBUG)

@app.route("/")
def index():
    if not google.authorized:
        return redirect(url_for("google.login"))
    resp = google.get("https://www.googleapis.com/oauth2/v1/userinfo")
    assert resp.ok, resp.text
    return resp.text

if __name__ == "__main__":
    app.run()
  1. start the app and authenticate once (maybe using an incognito browser session). Over here this causes the following output:
INFO:werkzeug:127.0.0.1 - - [27/Nov/2015 15:03:16] "GET / HTTP/1.1" 302 - 
DEBUG:requests_oauthlib.oauth2_session:Generated new state xxx. 
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=xxx.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fgoogle%2Fauthorized&scope=profile+email&state=xxx 
INFO:werkzeug:127.0.0.1 - - [27/Nov/2015 15:03:16] "GET /login/google HTTP/1.1" 302 - 
DEBUG:requests_oauthlib.oauth2_session:Requesting url https://accounts.google.com/o/oauth2/token using method POST. 
DEBUG:requests_oauthlib.oauth2_session:Supplying headers {'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'} and data {'redirect_uri': 'http://localhost:5000/login/google/authorized', 'code': 'xxx', 'client_id': 'xxx.apps.googleusercontent.com', 'client_secret': 'xxx', 'grant_type': 'authorization_code'} 
DEBUG:requests_oauthlib.oauth2_session:Passing through key word arguments {'timeout': None, 'verify': True, 'json': None, 'auth': None}. 
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): accounts.google.com 
DEBUG:requests.packages.urllib3.connectionpool:"POST /o/oauth2/token HTTP/1.1" 200 None 
DEBUG:requests_oauthlib.oauth2_session:Prepared fetch token request body grant_type=authorization_code&code=xxx&client_id=xxx.apps.googleusercontent.com&client_secret=xxx&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fgoogle%2Fauthorized 
DEBUG:requests_oauthlib.oauth2_session:Request to fetch token completed with status 200. 
DEBUG:requests_oauthlib.oauth2_session:Request headers were {'Content-Length': '277', 'Accept': 'application/json', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.8.1', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Accept-Encoding': 'gzip, deflate'} 
DEBUG:requests_oauthlib.oauth2_session:Request body was redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fgoogle%2Fauthorized&code=xxx&client_id=xxx.apps.googleusercontent.com&client_secret=xxx&grant_type=authorization_code 
DEBUG:requests_oauthlib.oauth2_session:Response headers were {'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', 'Alt-Svc':
'quic=":443"; ma=604800; v="30,29,28,27,26,25"', 'X-Frame-Options': 'SAMEORIGIN', 'X-Content-Type-Options': 'nosniff', 'Content-Disposition': 'attachment; filename="json.txt"; filename*=UTF-8\'\'json.txt', 'Transfer-Encoding': 'chunked', 'Date': 'Fri, 27 Nov 2015 14:04:37 GMT', 'Server': 'GSE', 'Pragma': 'no-cache', 'Content-Encoding': 'gzip', 'X-XSS-Protection': '1; mode=block', 'Alternate-Protocol': '443:quic,p=1', 'Set-Cookie': 'xxx', 'Cache-Control': 'no-cache, no-store, max-age=0,
must-revalidate', 'P3P': 'CP="This is not a P3P policy! See https://support.google.com/accounts/answer/151657?hl=en for more info."', 'Content-Type': 'application/json; charset=utf-8'} and content { 
 "access_token" : "xxx", 
 "token_type" : "Bearer", 
 "expires_in" : 3600, 
 "id_token" : "xxx" 
}. 
DEBUG:requests_oauthlib.oauth2_session:Invoking 0 token response hooks. 
DEBUG:requests_oauthlib.oauth2_session:Obtained token {'access_token': 'xxx', 'id_token': 'xxx', 'expires_at': 1448636677.8191292, 'expires_in': 3600, 'token_type': 'Bearer'}. 
INFO:werkzeug:127.0.0.1 - - [27/Nov/2015 15:04:37] "GET /login/google/authorized?state=xxx&code=xxx HTTP/1.1" 302 - 
DEBUG:requests_oauthlib.oauth2_session:Invoking 0 protected resource request hooks. 
DEBUG:requests_oauthlib.oauth2_session:Adding token {'id_token': 'xxx', 'expires_in': 3600, 'expires_at': 1448636677.8191292, 'access_token': 'xxx', 'token_type': 'Bearer'} to request. 
DEBUG:requests_oauthlib.oauth2_session:Requesting url https://www.googleapis.com/oauth2/v1/userinfo using method GET. 
DEBUG:requests_oauthlib.oauth2_session:Supplying headers {'Authorization': 'Bearer xxx'} and data None 
DEBUG:requests_oauthlib.oauth2_session:Passing through key word arguments {'allow_redirects': True}. 
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.googleapis.com 
DEBUG:requests.packages.urllib3.connectionpool:"GET /oauth2/v1/userinfo HTTP/1.1" 200 None 
INFO:werkzeug:127.0.0.1 - - [27/Nov/2015 15:04:38] "GET / HTTP/1.1" 200 -
  1. forget about it for more than an hour (expires in 3600 seconds in my case)

  2. refresh the page and see the exception oauthlib.oauth2.rfc6749.errors.TokenExpiredError: (token_expired)

Facebook.authorized is True even though the user clicked "Cancel"

Hi! I'm writing a small prototype and came across a weird behavior.
I have these 2 pieces of code:

blueprint = make_facebook_blueprint(
    client_id="...",
    client_secret="...",
    scope=['email'],
    redirect_url="/oauth2callback"
)

@app.route('/oauth2callback')
def oauth2callback():
    if facebook.authorized:
        me = facebook.get('me?fields=id,first_name,email').json()
        social_id, username, email = me['id'], me['first_name'], me['email']
        user = User.query.filter_by(social_id=social_id).first()
        if not user:
            user = User(social_id=social_id, nickname=username, email=email)
            db.session.add(user)
            db.session.commit()
        login_user(user, True)
    return redirect(url_for('index'))

And if the user clicks Cancel during the authorization dance the 'facebook.authorized' returns 'True'.

What am I doing wrong here? Thanks in advance!

Google and probably other non-spec compliant providers broken

This isn't something you can fix, but I figured I'd open an issue so other developers can avoid a maddening debug session.

requests-oauthlib started sending basic auth headers in 6.0.0, which is spec compliant but breaks a lot of existing providers (including Google).

The current work around is to lock requests-oauthlib to the previous version: requests-oauthlib==0.5.0

See requests/requests-oauthlib#218 (Google) and requests/requests-oauthlib#221 (LinkedIn)

Hopefully this can be closed after their next release.

InvalidClientIdError: (invalid_request) Missing required parameter: refresh_token

I'm getting the following error:

InvalidClientIdError: (invalid_request) Missing required parameter: refresh_token

when I call google.get("/plus/v1/people/me"). I was able to fetch the credentials initially, but when I revisit the page and try to fetch the credentials again, I run into this problem. I tried setting reprompt_consent=True and offline=True. I'm getting this error both locally and on Heroku.

The user is authorized (google.authorized is True). Is there another way to retrieve the user id and email?

versions

Flask==0.12.2
gunicorn==19.7.1
Flask-SQLAlchemy==2.3.2
psycopg2==2.7.4
flask_dance==0.13.0

error

File "/Library/Python/2.7/site-packages/flask/app.py", line 1997, in __call__
return self.wsgi_app(environ, start_response)
File "/Library/Python/2.7/site-packages/werkzeug/contrib/fixers.py", line 152, in __call__
return self.app(environ, start_response)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/Library/Python/2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/Library/Python/2.7/site-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/gabriel/Desktop/chess/main.py", line 163, in main
user_id, email = get_google_id_and_email()
File "/Users/gabriel/Desktop/chess/main.py", line 145, in get_google_id_and_email
response = google.get("/plus/v1/people/me")
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/requests/sessions.py", line 521, in get
return self.request('GET', url, **kwargs)
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/flask_dance/consumer/requests.py", line 135, in request
**kwargs
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/requests_oauthlib/oauth2_session.py", line 343, in request
self.auto_refresh_url, auth=auth, **kwargs
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/requests_oauthlib/oauth2_session.py", line 309, in refresh_token
self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 408, in parse_request_body_response
self.token = parse_token_response(body, scope=scope)
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 379, in parse_token_response
validate_token_parameters(params)
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 386, in validate_token_parameters
raise_from_error(params.get('error'), params)
File "/Users/gabriel/Library/Python/2.7/lib/python/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 415, in raise_from_error
raise cls(**kwargs)
InvalidClientIdError: (invalid_request) Missing required parameter: refresh_token

flask.session[state_key] can sometimes throw a key error

This can happen if network issues corrupt JSON or if you start reloading/URL hacking pages that do oauthy stuff.

Exception: KeyError: u'google_oauth_state'

Exception class: KeyError

Exception message: u'google_oauth_state'

Backtrace:
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1836 function: "__call__" text/code: "return self.wsgi_app(environ, start_response)"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1820 function: "wsgi_app" text/code: "response = self.make_response(self.handle_exception(e))"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1403 function: "handle_exception" text/code: "reraise(exc_type, exc_value, tb)"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1817 function: "wsgi_app" text/code: "response = self.full_dispatch_request()"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1477 function: "full_dispatch_request" text/code: "rv = self.handle_user_exception(e)"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1381 function: "handle_user_exception" text/code: "reraise(exc_type, exc_value, tb)"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1475 function: "full_dispatch_request" text/code: "rv = self.dispatch_request()"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask/app.py" line: 1461 function: "dispatch_request" text/code: "return self.view_functions[rule.endpoint](**req.view_args)"
filename: "/home/foo/.virtualenvs/foo/local/lib/python2.7/site-packages/flask_dance/consumer/oauth2.py" line: 204 function: "authorized" text/code: "self.session._state = flask.session[state_key]"

OAuth table's user_id column is null?

I've been following this video, which appears to be a slight extension of the information available in the docs. I've done some things a little differently from the way he does things in the video, and everything appears to be working, but the OAuth table's user_id column is null for the record that gets created.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.