Back-end
6 minute read

How to Create an SSO Button – A Flask Login Tutorial

Applications often need login functionality so that users can save data, create their own profiles, or maybe just to restrict access to sensitive resources. In a modern app, users expect to have standard login-related features like email verification, password reset, or multi-factor authentication. These features, though necessary, are not easy to get right and usually not the app’s main business.

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

Single sign-on (SSO), mostly known to users as login with social media buttons, were invented as a solution to this issue. For users, not going through another painful registration process was easy. For businesses, removing friction for users is always a huge win—and, for developers, all login-related features are now delegated to the identity provider (Facebook, Google, Twitter, etc.), meaning less code! Your app simply trusts the identity provider of doing its job of verifying user identity.

SSO is usually powered by the OpenId Connect (OIDC) or SAML protocol. SAML is used mostly in enterprise applications. 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.

In this Flask login tutorial, we’ll write a step-by-step guide to add an SSO login button into a Flask application with SimpleLogin and Facebook as the identity provider. This can be done without using any external library but in order to simplify the intricacies of OAuth, we’ll use Requests-OAuthlib, a library to integrate OAuth providers. If you are interested in implementing SSO from scratch, please check out Implement SSO Login – the raw way.

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

  • Homepage featuring login buttons
  • User information page where, upon successful login, the user will be able to see information such as name, email, and avatar

All the code for this tutorial can be found on flask-social-login-example repository.

A demo is also available at here. Feel free to remix the code on Glitch.

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:

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.

Login with SimpleLogin

Step 2: Identity Provider Credential

There are currently hundreds (if not thousands) of identity providers with the most popular ones being Facebook, Google, GitHub, and Instagram. For this post, SimpleLogin is chosen because of its developer-friendliness. The same code will work with any OAuth2 identity provider, though. (Disclaimer: I happen to be SimpleLogin’s co-founder, which—ahem—may have been a factor in my decision to use it.)

Please head to SimpleLogin and create an account if you do not have one already, then create a new app in the Developer tab.

On the app detail page, please copy your AppID and AppSecret and save them into the 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 good practice to save credentials into environment variables. This is also the third factor in the The Twelve Factors.

OAuth2 Settings

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.

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.

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:

# 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.

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 the 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 it for access token. The callback route is also responsible for displaying user information.

@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 the login button should bring you through the following flow. The full code can be found on GitHub at step3.py.

Login with SimpleLogin to Allow to user information

Login with Facebook

The setup of Facebook, Google, and Twitter login is a bit complex and requires additional steps like setting up SSL or choosing the right scopes. These are beyond the scope of this article.

Apart from a 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, 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:

Create a New App ID

Then choose “Integrate Facebook Login” on the next screen:

Integrate Facebook Login

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.

Basic settings

Update the client-id and client-secret.

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

Update the AUTHORIZATION_BASE_URL and TOKEN_URL:

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

The homepage:

@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.

# 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:

Valid OAuth Redirect URIs

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

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:

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.

Login with Facebook process

The full code is on facebook.py.

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 and state, which are important to defend against cross-site request forgery attacks. 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.

Happy OAuthing!

Understanding the basics

How do I create a login page in Flask?

A login page in Flask is basically an HTML page. The tricky part to handle login in Flask is maybe to store user login state in the session, which is usually handled by the flask-login extension. This extension also comes with the handy login_required decorator that restricts access to authenticated user.

What do you mean by single sign-on?

Single sign-on enables users to login only once and have access to multiple applications. For corporate users, this is usually under the form of an application portal. For private users, the most popular form of SSO is maybe the "Login with Facebook" button.

What is single sign-on and how does it work?

Single sign-on is a system where the authentication is "delegated" to another party called "identity provider." The communication between an application and the Identity provider is usually handled by the SAML protocol (mostly used in corporate environment) or OIDC/OAuth2 (mostly in consumer environment).

What are the benefits of single sign-on?

Single sign-on offers both advantages for users and application developers. Users don't have to remember yet another password and the sign-in is quick. For developers, they don't have to implement authentication-related features or worry about the security implications of storing user email/password.

Is OAuth single sign-on?

OAuth is an authorization protocol that dictates how an application can have access to user resources. OIDC (OpenID Connect), an "authentication" protocol, is created as a layer on top of OAuth. OIDC, with SAML, are usually the protocols that power single sign-on.

How do you test SSO?

You would need an Identity provider to test the SSO. There are thousands of Identity providers and most of them follow OIDC. For applications that don't need social data specific data (Facebook feed, Twitter feed, etc), using SimpleLogin as Identity provider can be a quick solution because of its "developer-friendliness."

Why is Flask used?

Flask is a popular web framework in Python. With its expressiveness, usually code written in Flask can be easily translated into other languages and frameworks.

Why is Flask a microframework?

The "micro" in microframework means Flask focuses only on the web and doesn't include things like database ORM, cache management, or authentication. These features are provided as extensions that can be enabled according to your needs.