2014-11-17T15:40:16Z

OAuth Authentication with Flask

Many web sites offer users the option to use a streamlined single-click registration and login built on third party authentication services, typically run by the big social networks. In my Flask Mega-Tutorial I showed you how to use one of these protocols, called OpenID.

In this article I want to give you an introduction to the OAuth protocol, which these days has replaced OpenID as the preferred third party authentication mechanism. I will also show you a complete Flask application that implements "Sign In with Facebook" and "Sign In with Twitter" functionality. With these two implementations as a guide you should find it easy to add any other OAuth providers you may need.

Brief Introduction to OAuth

The best way to explain OAuth is by going over the list of events that occur during the sign in process:

  1. The user navigates to the application's home page, say http://www.example.com, and clicks the "Sign in with Facebook" button, which links to an application route, for example http://www.example.com/authorize/facebook.
  2. The server receives the request and responds with a redirect to Facebook's OAuth authorization URL. All OAuth providers must document a URL to redirect the user to.
  3. The user is now prompted to login to Facebook (if not logged in already). Then a request to share information is presented, where the user needs to give Facebook permission to share the requested information with the originating application. This is all done at Facebook's website and is a private transaction between Facebook and the user, the application does not participate.
  4. Once the user accepts the request to share information, Facebook redirects back to the application at a pre-configured callback URL, for example http://www.example.com/callback/facebook. The query string of the redirect URL includes an authorization code that the application can use to access the Facebook API on behalf of the user.
  5. The application uses the Facebook API to obtain user information. Of particular interest is a unique identifier for the user, which can be used to register the user in the application's database, and once the user is registered to perform a login.

You can see above that the exchange between the application and the third party service is not trivial, but for the user it is extremely simple, since all the user needs to do is log in to the third party site and give permission to share information with the application.

There are two versions of the OAuth protocol currently in use, both following the overall process described above but with some implementation differences. OAuth 1.0a, used by Twitter, is the most complex of the two. OAuth 2, used by Facebook, is a backwards incompatible revision of the protocol that eliminates much of the complexity of version 1.0a by relying on secure HTTP for encryption.

Registration with OAuth Providers

Before an application can use a third party OAuth provider it needs to register with it. For Facebook and Twitter this is done on their respective developer sites with the creation of an "app" that represents the application for users of these sites.

To create a Facebook app you can visit https://developer.facebook.com. Select "Add a New App" from the Apps dropdown, and make the type "WWW/Website". Then enter a name and category for your app. Once the application is created, go to the "App Configuration" section and set the URL of the application, which in the case of you running it on your own computer will be http://localhost:5000.

For Twitter the location is https://apps.twitter.com. You will be asked to provide the app name, description, website and callback URL. For the last two you can enter placeholders, for example http://example.com and http://example.com/callback/twitter.

Note: If you want to use other OAuth providers you will need to find the appropriate procedure to register an application in their developer documentation.

The newly created app will be assigned two codes, usually called "id" and "secret". These identify the application that is making the authentication request, and are passed in the query string of the redirect URL to the provider site, in step 2 above.

OAuth Authentication Example

In the following sections I'm going to describe a relatively simple Flask application that implements Facebook and Twitter authentication.

I'm only going to show you the important parts of the application in the article, but the complete application is available on this GitHub repository: https://github.com/miguelgrinberg/flask-oauth-example. At the end of this article I show you the instructions on how to run it.

User Model

The users of the example application are stored in a SQLAlchemy database. The application uses the Flask-SQLAlchemy extension to work with the database, and the Flask-Login extension to keep track of logged in users.

from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin

db = SQLAlchemy(app)
lm = LoginManager(app)

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    social_id = db.Column(db.String(64), nullable=False, unique=True)
    nickname = db.Column(db.String(64), nullable=False)
    email = db.Column(db.String(64), nullable=True)

@lm.user_loader
def load_user(id):
    return User.query.get(int(id))

The database has a single table for the users. In addition to the id that is the primary key, the users table contains three columns:

  • social_id: a string that defines a unique identifier from the third party authentication service used to login.
  • nickname: a nickname for the user. Must be defined for all users, and does not need to be unique.
  • email: the email address of the user. This column is optional.

The model also inherits from UserMixin from Flask-Login, as that gives it the methods required by that extension. The user_loader callback function, also required by Flask-Login, loads a user by its primary key.

OAuth Implementation

There are several OAuth client packages for Python. For this example I have decided to use Rauth. But even when using an OAuth package, there are many aspects of the authentication against OAuth service providers that are left up to each provider to implement, which makes the task harder.

First of all, there are the two versions of the OAuth protocol, both widely used. But even among different providers using the same OAuth version, there are many details that are not part of the specification and need to be done according to the provider's own documentation.

For this reason I have decided to implement an abstraction layer on top of Rauth, so that the Flask application can be written generically. Below you can see a simple base class under which the provider specific implementations will be written:

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 FacebookSignIn(OAuthSignIn):
    pass

class TwitterSignIn(OAuthSignIn):
    pass

The OAuthSignIn base class defines the structure that the subclasses that implement each provider must follow. The constructor initializes the provider's name, and the application id and secret assigned by it, which are obtained from the configuration. Below you can see how the example application is configured (of course you will need to replace these codes with your own when you try this application):

app.config['OAUTH_CREDENTIALS'] = {
    'facebook': {
        'id': '470154729788964',
        'secret': '010cc08bd4f51e34f3f3e684fbdea8a7'
    },
    'twitter': {
        'id': '3RzWQclolxWZIMq5LJqzRZPTl',
        'secret': 'm9TEd58DSEtRrZHpz2EjrV9AhsBRxKMo8m3kuIZj3zLwzwIimt'
    }
}

At a high level there are two significant events supported by this class that are common to all OAuth providers:

  1. Initiation of the authentication process. For this the application needs to redirect to the provider's web site to let the user authenticate there. This is represented by the authorize() method.
  2. Once the authentication is completed the provider redirects back to the application. This is handled in the callback() method. Since the provider does not have direct access to the internal methods of the application, it will be redirecting to a URL that will call it. The URL that the provider needs to redirect to is returned by the get_callback_url() method and is built using the provider name, so that each provider gets its own dedicated route.

The get_provider() class method is used to lookup the correct OAuthSignIn instance given a provider name. This method uses introspection to find all the OAuthSignIn subclasses, and then saves an instance of each in a dictionary.

OAuth Authentication with Rauth

Rauth represent OAuth providers with an object of class OAuth1Service or OAuth2Service, depending on the version of the protocol that it uses. I create an object of this class in each provider's OAuthSignIn subclass. The implementations for Facebook and Twitter are shown below:

class FacebookSignIn(OAuthSignIn):
    def __init__(self):
        super(FacebookSignIn, self).__init__('facebook')
        self.service = OAuth2Service(
            name='facebook',
            client_id=self.consumer_id,
            client_secret=self.consumer_secret,
            authorize_url='https://graph.facebook.com/oauth/authorize',           
            access_token_url='https://graph.facebook.com/oauth/access_token',
            base_url='https://graph.facebook.com/'
        )

class TwitterSignIn(OAuthSignIn):
    def __init__(self):
        super(TwitterSignIn, self).__init__('twitter')
        self.service = OAuth1Service(
            name='twitter',
            consumer_key=self.consumer_id,
            consumer_secret=self.consumer_secret,
            request_token_url='https://api.twitter.com/oauth/request_token',
            authorize_url='https://api.twitter.com/oauth/authorize',
            access_token_url='https://api.twitter.com/oauth/access_token',
            base_url='https://api.twitter.com/1.1/'
        )

For Facebook, a provider that implements OAuth 2, the OAuth2Service class is used. The service object is initialized with the name of the service and several OAuth specific arguments. The client_id and client_secret arguments are the ones assigned to the application in Facebook's developer site. The authorize_url and access_token_url are URLs defined by Facebook for applications to connect to during the authentication process. Finally, the base_url sets the prefix URL for any Facebook API calls once the authentication is complete.

Twitter implements OAuth 1.0a, so class OAuth1Service is used instead. In OAuth 1.0a the id and secret codes are called consumer_key and consumer_secret, but are otherwise identical in functionality to the OAuth 2 counterparts. The OAuth 1 protocol requires providers to expose three URLs instead of two, there is an additional one called request_token_url. The name and base_url arguments are identical to the ones used in OAuth 2 services. I should note that Twitter offers two options for the authorize_url parameter. The URL shown above, https://api.twitter.com/oauth/authorize, is the most secure, as it will present the user with a screen in which they need to give permission to the app to access Twitter every time. Changing that URL to https://api.twitter.com/oauth/authenticate will make Twitter ask for permission just the first time, and then will silently allow access for as long as the user is logged in to Twitter.

Note that there is no standardization for the OAuth entry point URLs, OAuth providers define these as they like. To add a new OAuth provider you will need to obtain these URLs from the provider's documentation.

OAuth Authorization Phase

When the user clicks the "Login in with ..." link to initiate an OAuth authentication the following application route is invoked:

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

This route first ensures that the user is not logged in, and then simply obtains the OAuthSignIn subclass appropriate for the given provider, and invokes its authorize() method to initiate the process. The authorize() implementation for Facebook and Twitter is shown below:

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

class TwitterSignIn(OAuthSignIn):
    # ...
    def authorize(self):
        request_token = self.service.get_request_token(
            params={'oauth_callback': self.get_callback_url()}
        )
        session['request_token'] = request_token
        return redirect(self.service.get_authorize_url(request_token[0]))

For OAuth 2 providers like Facebook the implementation simply issues a redirect to a URL generated by rauth's service object. The scope is provider specific, in this case I am asking that I want Facebook to provide the user's email. The response_type=code argument tells the OAuth provider that the application is a web application (there are other possible values for different authentication workflows). Finally, the redirect_uri argument is set to the application route that the provider needs to invoke after it completes the authentication.

OAuth 1.0a providers use a slightly more complicated process that involves obtaining a request token from the provider, which is a list of two items, the first of which is then used as an argument in the redirect. The entire request token is saved to the user session because it will be needed again in the callback.

OAuth Callback Phase

The OAuth provider redirects back to the application after the user authenticates and gives permission to share information. The route that handles this callback is shown below:

@app.route('/callback/<provider>')
def oauth_callback(provider):
    if not current_user.is_anonymous():
        return redirect(url_for('index'))
    oauth = OAuthSignIn.get_provider(provider)
    social_id, username, email = oauth.callback()
    if social_id is None:
        flash('Authentication failed.')
        return redirect(url_for('index'))
    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'))

This route instantiates the OAuthSignIn provider class and invokes its callback() method. This method has the function to complete the authentication with the provider and obtain the user information. The return value is a tuple with three values, a unique id (called social_id to differentiate it from the id primary key), the user's nickname and the user's email. The id and the nickname are mandatory, but in this example application I made the email optional, since Twitter never shares that information with applications.

The user is searched in the database by the social_id field, and if not found, a new user is added to the database with the information obtained from the provider, effectively registering new users automatically. The user is then logged with the login_user() function of Flask-Login, and finally redirected to the home page.

The implementation of the callback() method for the Facebook and Twitter OAuth providers is shown below:

class FacebookSignIn(OAuthSignIn):
    # ...
    def callback(self):
        def decode_json(payload):
            return json.loads(payload.decode('utf-8'))

        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=decode_json
        )
        me = oauth_session.get('me').json()
        return (
            'facebook$' + me['id'],
            me.get('email').split('@')[0],  # Facebook does not provide
                                            # username, so the email's user
                                            # is used instead
            me.get('email')
        )

class TwitterSignIn(OAuthSignIn):
    # ...
    def callback(self):
        request_token = session.pop('request_token')
        if 'oauth_verifier' not in request.args:
            return None, None, None
        oauth_session = self.service.get_auth_session(
            request_token[0],
            request_token[1],
            data={'oauth_verifier': request.args['oauth_verifier']}
        )
        me = oauth_session.get('account/verify_credentials.json').json()
        social_id = 'twitter$' + str(me.get('id'))
        username = me.get('screen_name')
        return social_id, username, None   # Twitter does not provide email

In the callback() method the provider passes a verification token that the application can use to contact the provider's APIs. In the case of OAuth 2 this comes as a code argument, while for OAuth 1.0a it is oauth_verifier, both given in the query string. This code is used to obtain an oauth_session with the provider from the service object from rauth.

Note that in recent versions of the Facebook API, the session token is returned in JSON format. The default format that rauth expects for this token is to be provided in the query string of the request instead. For that reason, it is necessary to add a decoder argument that points to a function that decodes the JSON payload. In Python 2, it is sufficient to pass json.loads, but in Python 3 we need an additional step because the payload is returned as bytes, which the json parser does not understand. The conversion from bytes to string is done in the decode_json inner function.

The oauth_session object can be used to make API requests to the provider. Here it is used to request user information, which has to be done in a provider specific way. Facebook exposes a user id and an email, but does not give out usernames, so the username for the application is created from the left portion of the email address. Twitter provides the id and the username, but does not share emails, so the email is returned as None. The data obtained from the provider is finally returned as a three element tuple to the view function.

Note how in both cases the id value from the provider is prepended with "facebook$" or "twitter$" before it is returned, to make it unique across all providers. Since this is what the application will store as social_id in the database, it is necessary to do this to ensure that two providers that assign the same id to two different users do not collide in the application's database.

Conclusion

As I mentioned above, the example application allows any user to register and login with either a Facebook or a Twitter account. The application demonstrates how to register and login users without them having to enter any information, all they need to do is to login with the provider and authorize the sharing of information.

If you want to try this example, you need to follow some preparatory steps:

  • Clone or download the project's repository: https://github.com/miguelgrinberg/flask-oauth-example
  • Create a virtual environment and install the packages in the requirements.txt file (you can use Python 2.7 or 3.4).
  • Register "apps" with Facebook and Twitter, as described above.
  • Edit app.py with the id and secret codes of your Facebook and Twitter apps.

After you complete these instructions, you can run the application with python app.py, and then visit http://localhost:5000 in your browser.

I hope this article is useful in demystifying OAuth a little bit. If you have any questions feel free to write them below.

Miguel

167 comments

  • #151 arend said 2019-02-17T17:03:30Z

    Hei Miguel, Thanks for article. Everything words fine when im running the example, even tried adding google oauth which works fine. The problem is that when im trying to run this example with another client (react app) and returning json respond it seems as if the call back function is not running and everything stops in the authorize function. I guess there is some silly misunderstanding from my part but is it possible to get a pointer ?

  • #152 Miguel Grinberg said 2019-02-17T23:54:49Z

    @arend: I don't understand what you mean by returning a json response. The interaction between the server and the auth provider cannot be changed, if you are replacing the redirects, then that's the problem, those redirects are a core part of the OAuth protocol. Once authentication is done and the callback is invoked, you can log your user in and then load your react app, but the app will not be able to run uninterrupted during the authentication phase due to those redirects.

  • #153 arend said 2019-02-18T16:36:14Z

    I havent change the redirects in your example, the only different as far as i can see is that instead of calling the authorize/ route from the index.html template im calling it by a axios call from a react app client

  • #154 Miguel Grinberg said 2019-02-18T18:01:41Z

    @arend: Does your client honor redirects that come back in the Ajax responses?

  • #155 ibrahim said 2019-04-12T12:44:40Z

    Hi miguel love what u r doing to the community. Can you please tell me is this my error or facebook upgraded security Sorry for the bad English

    { "error": { "message": "Can't load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and sub-domains of your app to the App Domains field in your app settings.", "type": "OAuthException", "code": 191, "fbtrace_id": "HZ7dMzwfvr1" } }

  • #156 Miguel Grinberg said 2019-04-12T18:36:43Z

    @ibrahim: What facebook is telling you is that your callback URL points to a website that hasn't been registered in your Facebook's OAuth account. Callback URLs cannot be to a random website as that would make attacks possible, so Facebook needs to know in advance what is the domain of your callback.

  • #157 Flask-Dance said 2019-12-04T04:29:26Z

    Hi Miguel, would you consider adding a reference to Flask-Dance in this blog post. I started with your blog post on adding OAuth for my app. Soon ran into all the new restrictions like https and proxy etc, and I had to resort to Flask-Dance. The resulting code became much simpler and didn't have to modify any of my existing database models. All the Oauth code ended up as special routes inside the auth blueprints and that was it.

  • #158 deepak said 2020-02-28T08:10:28Z

    Hi Miguel, i have some questions how can i pass user id for saving the data into database according to the userid? someone please guide me. Thanks

  • #159 Miguel Grinberg said 2020-02-28T11:46:29Z

    @deepak: where do you need to pass the user_id? In general you can look it up directly by using the email, which your auth provider gives you.

  • #160 Hector Perez said 2020-04-14T22:40:08Z

    Hi Miguel.

    Looking for provider code, and from my understanding this is great , but for a client definition.

    Can you point me to a good resource or way of approaching creating a provider server?

    Thanks! Hector

  • #161 Miguel Grinberg said 2020-04-15T23:18:08Z

    @Hector: take a look at oathlib.

  • #162 Luis said 2020-04-17T14:58:09Z

    Hello Miguel,

    I am trying to implement a Google login using the same structure you made. But i get this error when im trying go back to my web service (callback):

    raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

    This error is from the line : me = oauth_session.get('me').json()

    Do you know what it can be ?

    Thank you.

  • #163 Miguel Grinberg said 2020-04-17T22:48:19Z

    @Luis: each provider has its own API to get user information, that is not part of the OAuth spec. Notice how Twitter and Facebook use different methods in this part. You have to find how to do this using the Google API.

  • #164 Joe said 2020-05-07T21:14:48Z

    Miguel - another great Flask tutorial. Thank you.

    This is several years old now. Has it been updated to keep up with current best practices, security and updates to the packages?

  • #165 Miguel Grinberg said 2020-05-07T22:22:27Z

    @Joe: what updates do you have in mind? The OAuth specification hasn't changed.

  • #166 Munkhzul said 2020-07-13T19:52:44Z

    Hi Miguel,

    Thanks for sharing amazing tutorials. I am wondering, if user closes the browser tab when google asks to authenticate. If user cancels to authenticate, how can I handle this kind of situations?

  • #167 Miguel Grinberg said 2020-07-14T14:16:11Z

    @Munkhzul: if the user closes the browser before the authentication process is complete, then nothing happen. The server does not store any state for the user, until the login process is complete, so there is no need to do any cleanup.

Leave a Comment