Tutorial: Creating an SSO Button with Flask Login

Many applications implement login systems to allow users to preserve their data, maintain personal profiles, or access restricted content. Modern applications typically offer features like email verification, password resets, and multi-factor authentication to enhance security and user experience. While crucial, these features are complex to implement correctly and often detract from the app’s core functionality.

Users also find the registration process tedious, requiring them to manage numerous email and password combinations. This often leads to password reuse, posing significant security risks.

Single sign-on (SSO), commonly recognized as “login with social media” buttons, emerged to address these challenges. SSO simplifies the login process for users while reducing user friction for businesses. For developers, SSO delegates the complexities of user authentication to established identity providers like Facebook, Google, or Twitter. This simplifies development by reducing the code required and relying on the identity provider’s authentication process.

OpenID Connect (OIDC) and SAML are the protocols underpinning SSO. While SAML finds its niche in enterprise applications, OIDC, built on OAuth2, is favored by social identity providers. This article focuses on OIDC/OAuth2 for SSO implementation.

This Flask login tutorial provides a step-by-step guide to integrating an SSO login button into a Flask application. We’ll be using SimpleLogin as the identity provider and Requests-OAuthlib to streamline OAuth integration. For those interested in a from-scratch implementation, refer to Implement SSO Login – the raw way.

By the end of this tutorial, you will have a Flask application with the following:

  • A homepage featuring login buttons
  • A user information page displaying user details like name, email, and avatar, accessible after successful login

The complete code for this tutorial is available on flask-social-login-example repository.

A live demo can be accessed at here. Feel free to experiment with the code on Glitch.

Step 1: Setting Up the Flask App

Begin by installing flask and Requests-OAuthlib. Consider using virtualenv or pipenv for environment isolation.

pip install flask requests_oauthlib

Create app.py and define the route for displaying the login button on the homepage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import flask

app = flask.Flask(__name__)

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

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

Run the application to ensure everything functions correctly:

1
python app.py

Accessing http://localhost:5000 should now display the homepage. The complete code for this step is available at step1.py.

Login with SimpleLogin

Step 2: Obtaining Identity Provider Credentials

With numerous identity providers available, this tutorial will utilize SimpleLogin due to its user-friendly developer experience. The code remains adaptable to other OAuth2 providers. (Full disclosure: I am a co-founder of SimpleLogin, which influenced this choice.)

Create an account on SimpleLogin and navigate to the Developer tab to create a new app.

Within the app details page, locate and copy your AppID and AppSecret. These values represent the OAuth client-id and client-secret and should be stored securely as environment variables. This approach enhances security and represents the third factor in the The Twelve Factors.

OAuth2 Settings
1
2
export CLIENT_ID={your AppID}
export CLIENT_SECRET={your AppSecret}

In your app.py file, add the following lines to retrieve the client id and client secret from environment variables:

1
2
3
import os
CLIENT_ID = os.environ.get("CLIENT_ID")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")

Additionally, include the following OAuth URLs at the top of app.py. These URLs are available on the OAuth endpoints page.

1
2
3
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"

To avoid setting up SSL for now, configure Requests-OAuthlib to permit plain HTTP:

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

As before, the code for this step can be found at step2.py.

Step 3: Implementing Login Redirection

When a user clicks the login button:

  1. They are redirected to the identity provider’s authorization page. This page prompts the user to grant your app access to their information.
  2. Upon user approval, they are redirected back to your application with a code parameter in the URL. This code is then exchanged for an access token, granting access to user information from the provider.

Therefore, two routes are required: a login route to redirect users to the identity provider and a callback route to handle the code exchange for an access token and display user information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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 the login button should initiate the following flow. The complete code for this step is available at GitHub at step3.py.

Login with SimpleLogin to Allow to user information

Integrating Facebook Login

Integrating Facebook, Google, or Twitter login involves additional complexities like SSL configuration and scope management, exceeding this tutorial’s scope.

Beyond its complex UI, Facebook integration is further complicated by the requirement for HTTPS when running locally, as the latest Facebook SDK disallows plain HTTP. Using Ngrok provides a convenient solution for obtaining a quick HTTPS URL.

Step 1: Creating a Facebook App

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

Create a New App ID

Select “Integrate Facebook Login” on the following screen.

Integrate Facebook Login

Step 2: Obtaining Facebook OAuth Credentials

Navigate to “Settings/Basic” on the left side menu and copy your App ID and App Secret, representing the OAuth client-id and client-secret, respectively.

Basic settings

Update the client-id and client-secret values in your code.

1
2
export FB_CLIENT_ID={your facebook AppId}
export FB_CLIENT_SECRET={your facebook AppSecret}

Update the AUTHORIZATION_BASE_URL and TOKEN_URL with the Facebook-specific endpoints:

1
2
FB_AUTHORIZATION_BASE_URL = "https://www.facebook.com/dialog/oauth"
FB_TOKEN_URL = "https://graph.facebook.com/oauth/access_token"

Modify the homepage code as follows:

1
2
3
4
5
@app.route("/")
def index():
	return """
	<a href="/fb-login">Login with Facebook</a>
	"""

Step 3: Configuring Login and Callback Endpoints

If you’re using ngrok to expose your local server, for example, with ngrok http 5000, ensure that the current URL reflects the ngrok URL.

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

Add this URL to your Facebook Login/Settings under Valid OAuth Redirect URIs.

Valid OAuth Redirect URIs

To access user email addresses, include email in the scope:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 requires modifications to address a Facebook compliance requirement:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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>
	"""

You should now be able to complete the Facebook login flow.

Login with Facebook process

The complete code for Facebook integration is available at facebook.py.

Conclusion

Congratulations on successfully integrating SSO login into your Flask application!

For simplicity, this tutorial omits other important OAuth concepts like scope and state that are crucial for preventing cross-site request forgery attacks. Storing user information in a database, which was not covered here, is another important consideration for real-world applications.

Remember that production environments require HTTPS. Thankfully, services like Let’s Encrypt make this process straightforward.

Best of luck with your future OAuth endeavors!

Licensed under CC BY-NC-SA 4.0