Python - Flask

This tutorial will walk you through creating a simple Python + Flask application using Swoop for user authentication.

You can download the comprehensive Starter Example Here if you prefer.

Before you begin, head to the Swoop Dashboard and create a new property. Make sure to set the Redirect URI to the following to work with our test project:
https://localhost:5000/auth/swoop/callback.

1. Setting Up a Basic Python Flask App

We are going to make a simple app that has one protected route called logged_in.

mkdir swoop-python-flask
cd swoop-python-flask
mkdir templates

Set up a Python virtual environment and install Flask and other libraries we need for the integration.

python3 -m venv venv
source venv/bin/activate
pip install Flask requests requests_oauthlib cryptography pyjwt

Now, create the file index.py and add the following code:

from flask import Flask, request, redirect, session, render_template, url_for, flash

app = Flask(__name__)
app.secret_key = 'Flask secret key'

# Routes...

@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html')

@app.route('/logged_in', methods=['GET'])
def logged_in():
    email = "user"
    if session.get('email'):
        email = session['email']
    return render_template('logged_in.html', email=email)

@app.route('/logout', methods=['GET'])
def logout():
    return redirect(url_for('index'))

2. Create the Application's Views

Create the file base.html in the templates directory. This file will be included by the index and logged_in pages. Add the following code to the base.html file:

<!doctype html>
<html lang="en">

	<head>
	    <title>Hello Swoop!</title>
	    <style> body { font-family: Arial, serif; margin: 35px; } </style>
	</head>

	<body>
		<p><a href="{{ url_for('index') }}">Home</a> | <a href="{{ url_for('logged_in') }}">Secret</a> | <a href="{{ url_for('logout') }}">Log out</a></p>
		<div id="content">{% block content %}{% endblock %}</div>
	</body>

</html>

Create the file index.html in the templates directory and add the following code:

{% extends "base.html" %}
{% block content %}
    <h2>Hello Swoop!</h2>
{% endblock %}

Create the file logged_in.html in the templates directory and add the following code:

{% extends "base.html" %}
{% block content %}
	<h2>Shhhhh! It's a secret!</h2>
  <p>Welcome, {{ email }}</p>
{% endblock %}

At this point we have a basic Flask application that has three routes: a root route, a secret route, and a logout route.

Run this code by executing the following command in the terminal:

export FLASK_APP=index.py
flask run

Open your browser to http://localhost:5000/logged_in and you will see the output "Shhhhh! It's a secret!" below a simple menu. We will now set up Swoop to secure this route and require a user to login to view this resource.

3. Setting Up OAuth 2

We will use the requests-oauthlib library to help us with the OAuth integration. Given a little configuration, it will help us generate the OAuth link to send our users to Swoop and will handle the callback when Swoop sends the authorization code.

We are also using the PyJWT library in order to decode the JWT sent back from Swoop to identify the user.

Add the following code to the top of index.py:

from requests_oauthlib import OAuth2Session
import jwt

This imports the requests-authlib and PyJWY libraries. Now we are going to add code to prepare for the authentication call to Swoop.

Above the 'Routes' section in index.py, add the following:

# OAuth details
authorization_base_url = 'https://auth.swoop.email/oauth2/authorize'
token_url = 'https://auth.swoop.email/oauth2/token'

# Add your Swoop account details
client_id = 'CLIENT_ID'
client_secret = 'CLIENT_SECRET'
callback_url = 'http://localhost:5000/auth/swoop/callback'

# Routes...

There are 3 properties here to pay attention to:
-CLIENT_ID The Client ID associated with your Swoop Property.
-CLIENT_SECRET The Client Secret that was generated for your Swoop Property.
-callback_url In our app this will just point to localhost, however this will eventually by updated to your production endpoint.

Since our app will use a non-secure callback route, we need to let the OAuth library know about it. After the import statements at the top of index.py, add the following code:

import os

os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

Now, we will add a new route to initiate the Swoop login process. Add the following code below the existing routes in index.py:

@app.route('/login', methods=['GET'])
def login():
    # Redirect the user to the Swoop OAuth server
    # using a URL with a the correct OAuth parameters.

    global authorization_base_url
    global client_id

    swoop = OAuth2Session(client_id)
    authorization_url, state = swoop.authorization_url(authorization_base_url)

    # build the authorization URL with the required query parameters
    authorization_url += '?response_type=code&redirect_uri=' + callback_url + '&scope=email'

    # state is used to prevent CSRF, keep for the callback
    session['oauth_state'] = state

    return redirect(authorization_url)

The login route begins the authentication process by creating an OAuth2Session object. We create the authorization URL and stash the state in the session dictionary for use in the callback function.

To reach the login route, let's add a button to the home page. Just below the "Hello Swoop!" tag in index.html, add the button inside a form:

<h2>Hello Swoop!</h2>
		<form id="login" method="GET" action={{ url_for('login') }}>
        <button type="submit">Log In</button>
    </form>

Next, we will create the callback endpoint for Swoop to call our application during the OAuth authentication process. Add the following code below the login route in index.py:

@app.route('/auth/swoop/callback')
def callback():
    # The user has been redirected back from Swoop to our callback URL.
    # With this redirection comes an authorization code included in the redirect URL.
    # We use that code to obtain an access token.

    try:
        state_from_request = request.args.get('state')
        code = request.args.get('code')
        swoop = OAuth2Session(client_id, state=session['oauth_state'],redirect_uri=callback_url)
        token = swoop.fetch_token(token_url, client_secret=client_secret, code=code, authorization_response=request.url)

        # get the user's email address from the web token
				payload = jwt.decode(token['id_token'], client_secret, audience=client_id, algorithms=["HS256"])
        email = payload['email']

        session.clear()
        session['email'] = email
        return redirect(url_for('logged_in'))

    except Exception as e:
        session.clear()
        return redirect(url_for('index'))

When the Swoop server hits our callback endpoint, we retrieve the state and authorization code from the request and use it to fetch an access token from the Swoop server.

We decode the response JWT and retrieve the user's email address. At this point, the user has been authenticated by Swoop using the OAuth process. Your actual application can validate the user however you like.

If the user is not valid, we clear the session and redirect the user to the index page. If the user is valid, we add their email address to the session and redirect to the logged_in route.

Add a line to the logout route in index.py to clear the session before redirecting the user to the index page. The modified logout route now looks like this:

@app.route('/logout', methods=['GET'])
def logout():
  	session.clear()
    return redirect(url_for('index'))

As a final touch to the UI, let's display the flash messages added in the previous section that instruct the user to login if they have not already done so.

Also, let's display the 'Log out' menu link only if the user is logged in. Modify the base.html file to look like this:

<!doctype html>
<html lang="en">

	<head>
	    <title>Hello Swoop!</title>
	    <style> body { font-family: Arial, serif; margin: 35px; } </style>
	</head>

	<body>
		<p>
			<a href="{{ url_for('index') }}">Home</a> | <a href="{{ url_for('logged_in') }}">Secret</a>
            {% if session['email'] %}
                | <a href="{{ url_for('logout') }}">Log out</a>
            {% endif %}
		</p>
        
		<div id="flash">
			{% with messages = get_flashed_messages() %}
				{% if messages %}
					<ul class="flash">
						{% for message in messages %}
							<li>{{ message }}</li>
						{% endfor %}
					</ul>
				{% endif %}
			{% endwith %}
		</div>
        
		<div id="content">{% block content %}{% endblock %}</div>
	</body>

</html>

4. Securing the Logged In Route

To secure the logged_in page, we will add Flask's before_request function, which is called on each inbound request before the appropriate route function is called.

Add the before_request function just above the root route in index.py:

@app.before_request
def before_request():
    # secure the 'logged in' page

    if request.endpoint == 'logged_in' and 'email' not in session:
        flash('Please login!')
        return redirect(url_for('index'))

Now when the user attempts to open "/logged_in", it will redirect them to the home page unless they have logged in. If the user is logged in, their email will be in the session.

Restart the server and go through the login flow again.

export FLASK_APP=index.py
flask run

You are now able to secure ANY route using the Swoop authentication service. Woot!