Create a Flask application with SSO login
Tags: flask, sso, python, tutorial
Published: 2019-09-13 00:28:01

It's often needed to have some sort of login functionality in an app so users can save data or create their own profiles. Or maybe only authenticated users can have access to reserved content. In a modern app, users expect to have standard login-related features like email verification, password reset, multi-factor authentication, etc. These features, though necessary, are not easy to get right and usually not the app's main business.

On the user side, they don't want to go through the lengthy registration process neither as they need to create and remember yet another email/password. Without a proper password manager, users tend to reuse the same password which is terrible in terms of security.

**SSO (Single sign-on)**, mostly known to the public by the everywhere **Login with Facebook/Google/Twitter** buttons, was invented as a solution for this issue. For users, they don't have to go through the painful registration process: one click is all it needs. For developers, removing friction for users is always a huge win and in addition, all login-related features are now *delegated* to the **Identity provider** (i.e. Facebook/Google/Twitter). Your app simply *trusts* the Identity provider of doing its job of verifying user identity.

SSO is usually powered by OIDC (OpenId Connect) or SAML protocol. SAML is used mostly in enterprise application. OIDC is built on top of OAuth2 and used by social identity providers like Facebook, Google, etc. In this post, we'll focus on the OIDC/OAuth2 protocol.

This post presents a step-by-step guide to add a SSO Login button into a Flask application with SimpleLogin and Facebook as Identity provider. This can be done without using any external library but in order not to worry too much about the OAuth details, we'll use [Requests-OAuthlib](https://github.com/requests/requests-oauthlib), a library to integrate OAuth providers. If you are interested in implementing a SSO login from scratch, please check out [Implement SSO Login the raw way](https://docs.simplelogin.io/docs/code-flow/).

At the end of this article, you should have a Flask app that has the following pages:

- Home page: that only has the **login** button
- User information page: upon successful login, the user will be able to see information such as name, email, avatar.

All steps of this tutorial can be found on [flask-social-login-example repository](https://github.com/nguyenkims/flask-social-login-example).

A demo is also available at https://nguyenkims-flask-social-login-example.glitch.me, feel free to **remix** the code on [Glitch](https://glitch.com/~nguyenkims-flask-social-login-example) 😉.

## Step 1: Bootstrap Flask app

Install `flask` and  `Requests-OAuthlib`. You can also use `virtualenv` or `pipenv` to isolate the environment.

> pip install flask requests_oauthlib

Create `app.py` and the route that displays a login button on the home page:

```python
import flask

app = flask.Flask(__name__)

@app.route("/")
def index():
    return """
    <a href="/login">Login</a>
    """

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

Let's run this app and verify everything is working well:

> python app.py

You should see this page when opening http://localhost:5000. The full code is on [step1.py](https://github.com/nguyenkims/flask-social-login-example/blob/master/step1.py)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/o6ydih137sm0cmzz6joc.png)

## Step 2: Identity provider credential

There are currently hundreds (if not thousands) identity providers with the most popular ones being Facebook, Google, Github, Instagram, etc. For this post, [SimpleLogin](https://simplelogin.io) is chosen because of its **developer-friendliness**. The same code will work with any OAuth2 identity provider (Facebook, Google, etc) though. Disclaimer: I happen to be SimpleLogin co-founder so this choice is obviously subjective.

As going through the setup of Facebook, Google, Twitter login is a bit complex (everyone uses Google Cloud Console can feel the pain 😅) and requires additional steps that are beyond the scope of this post like setting up SSL, choosing right scopes, etc. the section **Login with Facebook** is rather provided as an appendix at the end of the post.

Please head to [SimpleLogin](https://app.simplelogin.io) and create an account if you do not have one already, then create a new app in **Developer** tab. 

On app detail page, please copy your AppID and AppSecret and save them into variable environment. In OAuth terminology, **client** actually means a third-party app, i.e. your app. We can put these values directly in the code but it's a good practice to save credentials into **variable environments**. This is also the third factor in the [The Twelve Factors](https://12factor.net).

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/zeymjg5a5areaoh39dtw.png)

```bash
export CLIENT_ID={your AppID}
export CLIENT_SECRET={your AppSecret}
```

In `app.py` please add these lines on top of the file to get `client id` and `client secret`

```python
import os
CLIENT_ID = os.environ.get("CLIENT_ID")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
```

Please also add these OAuth URLs on the top of `app.py` that are going to be used in the next step. They can also be copied on the **OAuth endpoints** page.

```python
AUTHORIZATION_BASE_URL = "https://app.simplelogin.io/oauth2/authorize"
TOKEN_URL = "https://app.simplelogin.io/oauth2/token"
USERINFO_URL = "https://app.simplelogin.io/oauth2/userinfo"
```

As we don't want to worry about setting up SSL now, let's tell `Requests-OAuthlib` that it's OK to use plain HTTP:

```python
# This allows us to use a plain HTTP callback
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
```

As usual, the code for this step is on [step2.py](https://github.com/nguyenkims/flask-social-login-example/blob/master/step2.py)

## Step 3: Login redirection

When a user clicks on the login button:
1. The user will be redirected to the **identity login provider** authorization page asking whether user wants to share their information with your app.

2. Upon user approval, they will be then redirected back to a page on your app along with a `code` in the URL that your app will use to exchange for an `access token` that allows you later to get user information from the service provider.

We need therefore two routes: a `login` route that redirects the user to the identity provider and a `callback` route that receives the `code` and exchanges for `access token`. The callback route is also responsible for displaying user information.


```python
@app.route("/login")
def login():
    simplelogin = requests_oauthlib.OAuth2Session(
        CLIENT_ID, redirect_uri="http://localhost:5000/callback"
    )
    authorization_url, _ = simplelogin.authorization_url(AUTHORIZATION_BASE_URL)

    return flask.redirect(authorization_url)


@app.route("/callback")
def callback():
    simplelogin = requests_oauthlib.OAuth2Session(CLIENT_ID)
    simplelogin.fetch_token(
        TOKEN_URL, client_secret=CLIENT_SECRET, authorization_response=flask.request.url
    )

    user_info = simplelogin.get(USERINFO_URL).json()
    return f"""
    User information: <br>
    Name: {user_info["name"]} <br>
    Email: {user_info["email"]} <br>
    Avatar <img src="{user_info.get('avatar_url')}"> <br>
    <a href="/">Home</a>
    """
```

Clicking on **Login** button should bring you through the following flow. The full code can be found on [Github - step3.py](https://github.com/nguyenkims/flask-social-login-example/blob/master/step3.py)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/f23w5cnhuhqbjej12nak.png)

## Conclusion

Congratulations 🎉 you have successfully integrated **SSO login** into a Flask app!

For the sake of simplicity, this tutorial doesn't mention other OAuth concepts like [scope](https://oauth.net/2/scope/) and [state](https://spring.io/blog/2011/11/30/cross-site-request-forgery-and-oauth2),  important to defend against **Cross Site Request Forgery ** attack. You would also probably need to store the user info in a database which is not covered in this article.

The app also needs to be served on https on production, which can be quite easily done today with [Let’s Encrypt](https://letsencrypt.org). 

Happy OAuthing!

---
## Appendix: Login with Facebook

As promised earlier, please find below the steps to integrate **Login with Facebook** button 🙂. Apart from a quite sophisticated UI, the hardest part about integrating Facebook might be finding a way to serve your web app on https locally as the new version of Facebook SDK doesn't allow local plain HTTP. I recommend using [Ngrok](https://ngrok.com), a free tool to have a quick https URL.

### Step 1: Create a Facebook app:

Please head to https://developers.facebook.com and create a new app


![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/pi7i5raswmt9nwqpn01s.png)

Then choose "Integration Facebook Login" on the next screen

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/n4vvf2y1vq6mi2adboit.png)

### Step 2: Facebook OAuth credential

Click on "Settings/Basic" on the left and copy the *App ID* and *App Secret*, they are actually OAuth client-id and client-secret.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/8u6jlnoakwpc2ut28ll1.png)

Update the client-id and client-secret

```bash
export FB_CLIENT_ID={your facebook AppId}
export FB_CLIENT_SECRET={your facebook AppSecret}
```

Update the AUTHORIZATION_BASE_URL and TOKEN_URL:

```python
FB_AUTHORIZATION_BASE_URL = "https://www.facebook.com/dialog/oauth"
FB_TOKEN_URL = "https://graph.facebook.com/oauth/access_token"
```

The home page

```python
@app.route("/")
def index():
    return """
    <a href="/fb-login">Login with Facebook</a>
    """
```

### Step 3: Login and callback endpoints

If the app is served behind `ngrok` using `ngrok http 5000` command, we need to set the current url to the ngrok url

```python
# Your ngrok url, obtained after running "ngrok http 5000"
URL = "https://abcdefgh.ngrok.io"
```

Please make sure to add the url https://abcdefgh.ngrok.io/fb-callback to your Facebook Login/Settings, Valid OAuth Redirect URIs setting:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/vzs5e4lywrq0vasirw0x.png)

In order to have access to a user email, you need to add `email` into `scope`

```python
FB_SCOPE = ["email"]

@app.route("/fb-login")
def login():
    facebook = requests_oauthlib.OAuth2Session(
        FB_CLIENT_ID, redirect_uri=URL + "/fb-callback", scope=FB_SCOPE
    )
    authorization_url, _ = facebook.authorization_url(FB_AUTHORIZATION_BASE_URL)

    return flask.redirect(authorization_url)
```

The `callback` route is a bit more complex as Facebook requires a compliance fix:

```python
from requests_oauthlib.compliance_fixes import facebook_compliance_fix

@app.route("/fb-callback")
def callback():
    facebook = requests_oauthlib.OAuth2Session(
        FB_CLIENT_ID, scope=FB_SCOPE, redirect_uri=URL + "/fb-callback"
    )

    # we need to apply a fix for Facebook here
    facebook = facebook_compliance_fix(facebook)

    facebook.fetch_token(
        FB_TOKEN_URL,
        client_secret=FB_CLIENT_SECRET,
        authorization_response=flask.request.url,
    )

    # Fetch a protected resource, i.e. user profile, via Graph API

    facebook_user_data = facebook.get(
        "https://graph.facebook.com/me?fields=id,name,email,picture{url}"
    ).json()

    email = facebook_user_data["email"]
    name = facebook_user_data["name"]
    picture_url = facebook_user_data.get("picture", {}).get("data", {}).get("url")

    return f"""
    User information: <br>
    Name: {name} <br>
    Email: {email} <br>
    Avatar <img src="{picture_url}"> <br>
    <a href="/">Home</a>
    """
```

Now when clicking on **Login with Facebook**, you should be able to go through the whole flow.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/do5acziteu8fwxb5oesc.png)

The full code is on [facebook.py](https://github.com/nguyenkims/flask-social-login-example/blob/master/facebook.py)

Integrating `Login with Google/Twitter/...` is quite similar. Please let me know in the comment if you would like to have similar sections for these social login providers!