Authenticating a Flask API using Okta

Erika Dike
The Andela Way
Published in
11 min readJul 11, 2019
Photo by Ryan Quintal on Unsplash

Recently, I wrestled with making a choice for what authentication solution to include in a simple application I created for my company. The app was supposed to be used by a few people — all members of the same team. We already used Okta for identity management across the entire organization. I figured there was no need storing authentication information locally since Okta already had all the information we need. All I needed to do was find a way to allow users to authenticate using Okta.

In this tutorial, we are going to go over authenticating a Flask application over API using Okta as our identity server. Okta is a free-to-use API service that stores user accounts, and makes handling user authentication, authorization, social login, password reset, etc, — simple.

At the end of this article, you would have:

  • built a simple Flask API service
  • created an application on okta
  • used OpenID connect for user authentication

Set up Flask API

First, create and activate a virtual environment using your preferred approach. Here, I will go with the venv utility in python 3.

$ python3 -m venv --prompt='flask-okta' .virtualenv
$ source .virtualenv/bin/activate

Initialize requirements.txt with the following packages:

Flask>=1.0.0
flask-oidc==1.4.0
okta==0.0.4

Install these dependencies with pip install -r requirements.txt

Now that we have the basic dependencies installed, we are now ready to begin creating our application. This application will be pretty simple. We are going to have two endpoints: one endpoint to handle logins and another to greet the user.

First, let us create our endpoints: index and greet. We just want to confirm that we are able to access the endpoints before we add in the authentication layer.

Create a new file: app.py and enter the code below

from datetime import datetimefrom flask import Flaskapp = Flask(__name__)
@app.route('/')
def index():
return 'Welcome'
@app.route('/greet')
def greet():
time = datetime.now().hour
if time >= 0 and time < 12:
return 'Good Morning!'
elif time >= 12 and time < 16:
return 'Good Afternoon!'
else:
return 'Good Evening!'

Then run the app from the command line with FLASK_APP=app.py flask run If you encounter an error saying the command was not found deactivate your virtual environment and re-activate. That should clear that error up. If the error still shows up, try forcing the upgrade of the Flask dependency like so: pip install --upgrade Flask .

Once you get the server running, you can head to http://localhost:5000 on your browser. The page should contain the text ‘Welcome’. Also confirm that you can hit the /greet endpoint by visiting http://localhost:5000/greet, you receive text “Good Morning/Afternoon/Evening” depending on your current time.

Now that we have functioning endpoints, let us protect one of them by only allowing authenticated users access to the endpoint.

Create an OpenID connect application on Okta

At this point, we are ready to create an OpenID application on Okta for the Flask project. Applications in OpenID Connect have a username and password (referred to as a client ID and client secret) that allows your authorization server (in our case, Okta) to recognize which application is talking to it at any given time.

You will want to create a developer account if you do not have an account already. Once you have your developer account, sign in and go to the Applications tab. There click on Add Application

Next, click on the Web platform option, and click Next.

On the settings page, fill in the following values:

Leave Group Assignments as Everyone for now. We will restrict access to a group of users later in this tutorial. You can leave Grant type allowed unchanged.

Create an Authentication Token

In order to access the Okta API and manage the user accounts from the Flask API, we will need to create an Okta authentication token. This is a bearer token that should be included in our requests to Okta. This token will enable us to perform actions like:

  • Create, update and delete users
  • Create, update and delete groups
  • Manage application settings

To create an authentication token, Access the API page, and select Tokens from the API menu. Then click Create Token

Name your token, preferably the same name as the application so you can easily map a token to the application using it, then click Create Token when you are done.

Ensure you copy the token immediately and put it somewhere safe where you can reference it as you won’t be able to see it again.

Add Okta Authentication to the Flask Application

Now that we have both sides of the application ready to talk to each other, we will set up the Flask side with the code and configuration necessary to restrict access to our application’s greet endpoint to only authenticated users.

Craft your OpenID Connect Configuration File

First, we create a new file client_secrets.json in our project’s root folder and paste in the code below:

{
"web": {
"client_id": "{{ OKTA_CLIENT_ID }}",
"client_secret": "{{ OKTA_CLIENT_SECRET }}",
"auth_uri": "{{ OKTA_ORG_URL }}/oauth2/default/v1/authorize",
"token_uri": "{{ OKTA_ORG_URL }}/oauth2/default/v1/token",
"issuer": "{{ OKTA_ORG_URL }}/oauth2/default",
"userinfo_uri": "{{ OKTA_ORG_URL }}/oauth2/default/userinfo",
"redirect_uris": [
"http://localhost:5000/oidc/callback"
]
}
}

Remember to replace the placeholders with the actual values from Okta:

  1. OKTA_CLIENT_ID: To retrieve this value, go to the Applications tab and select Flask Okta Tutorial from the list of applications. Click on the General tab and scroll to the bottom of the page. There you will see the value for Client ID.
  2. OKTA_CLIENT_SECRET: This value will be immediately below the Client ID field on the General tab.
  3. OKTA_ORG_URL: You will find this value on the Dashboard tab.

client_secrets.json will be used by the Flask-OIDC package. It will use this information to connect to the Okta API. These settings basically tell the OpenID Connect library what OpenID Connect application you’re using to authenticate against, and what your authorization server API endpoints are.

The URI endpoints list the endpoints that our application will call to get authorized, fetch a token and fetch user information.

Configure Flask-OIDC

The next step is to add configuration information for the Flask-OIDC library. In this step, we are also going to tell the Flask-OIDC library where it can find the client_secrets.json file we created in the preceding step using the variable OIDC_CLIENT_SECRETS. Note that populating this variable is not necessary if you have both your flask settings file and the client_secrets.json file in the same folder.

Copy the new lines of configuration below to app.py

from flask import Flask
from flask_oidc import OpenIDConnect
app = Flask(__name__)app.config['OIDC_CLIENT_SECRETS'] = 'client_seecrets.json'
app.config['OIDC_COOKIE_SECURE'] = False
app.config['OIDC_CALLBACK_ROUTE'] = '/oidc/callback'
app.config['OIDC_SCOPES'] = ['openid', 'email', 'profile']
app.config['SECRET_KEY'] = '{{ LONG_RANDOM_STRINGS }}'
oidc = OpenIDConnect(app)

We define the function of each of the configuration variables below:

  • OIDC_COOKIE_SECRETS: indicates to Flask-OIDC where your OpenID Connect configuration file is located.
  • OIDC_COOKIE_SECURE: allows you to test out user login and registration and development without using SSL. If you are going to run the site publicly, you would remove this option and use SSL on your site.
  • OIDC_CALLBACK_ROUTE: URL relative to the web root to indicate where the oidc_callback URL is mounted on.
  • OIDC_SCOPES: tells Flask-OIDC what data to request about the user when they log in. Here, we are requesting basic user information.
  • SECRET_KEY: should be set to a long random string. This is used to secure your Flask session (cookies) so that nobody can tamper with them. Ensure that you keep this value private.

Finally, we initialize the Flask-OIDC package by creating the OIDC object.

Inject the User into Each Request

Paste the following code in app.py just before our index endpoint

from flask import Flask, g
from flask_oidc import OpenIDConnect
from okta import UsersClient
...
app.config['SECRET_KEY'] = '{{ LONG_RANDOM_STRINGS }}'
oidc = OpenIDConnect(app)
okta_client = UsersClient('{{ OKTA_ORG_URL }}', '{{ OKTA_AUTH_TOKEN }}')

@app.before_request
def inject_user_into_each_request()
if oidc.user_loggedin:
g.user = okta_client.get_user(oidc.user_getfield('sub'))
else:
g.user = None

The first thing we did here was to import the okta Python library and use it to create the okta_client object. This object is used in the inject_user_into_each_requestfunction to fetch a robust user object which can be used to:

  • Identify the currently logged in user
  • Make changes to the user’s account
  • Store and retrieve user information

Remember to replace the OKTA_ORG_URL placeholder and the OKTA_AUTH_TOKEN placeholder with real values. We used OKTA_ORG_URL in the client_secrets.jsonfile. Replace OKTA_AUTH_TOKEN with the token that was created in the Create an Authentication Token section of this tutorial.

In the function, inject_user_into_each_request we check if the request is from an authenticated user. If it is, we grab the user’s unique ID from the user’s session and then use that ID to fetch the user object from the Okta API. Whether we retrieve the information or not, we add the user variable to the gobject. The g object is a Flask object that we can use to store values that we want to be able to access from anywhere within our Flask application.

Enable User Login and Logout

Now that we have set up all the configuration and supporting code, we are now ready to add our login and logout endpoints. We will also be restricting access to the /greet endpoint so only authenticated users can be greeted :).

Add the following code toapp.py

...from flask import current_app, Flask, g, redirect, url_for...@app.route('/greet')
@oidc.require_login
def greet():
time = datetime.now().hour
if time >= 0 and time < 12:
return 'Good Morning!'
elif time >= 12 and time < 16:
return 'Good Afternoon!'
else:
return 'Good Evening!'
@app.route('/login')
@oidc.require_login
def login():
return redirect(url_for('.greet'))
@app.route('/logout')
def logout():
oidc.logout()
return redirect(url_for('.index'))

We have updated the greet endpoint by adding the decorator: @oidc.require_login. This decorator will cause a user to be redirected to the login endpoint if the user is not authenticated. The Login endpoint will in-turn redirect you to Okta’s login page where on successful authentication you would be brought back to the /greet endpoint on the Flask application. We also define the /logout endpoint which logs out a user by using the oidc.logout() method and then redirects the user to the /index endpoint.

You can go visit the /greet endpoint on the browser at http://localhost:5000/greet (preferably on an incognito session, as you might already be authenticated in your regular browser session). When on the Okta Login page, fill in your Okta credentials and you should then have access to the /greet endpoint.

Limiting Access to a Specific Group

There are situations when you want only a certain group of people to access your app even though they are valid users in your organization. Okta also supports this use case.

First, you want to go back to the application on Okta’s developer website. There, hover over Users and select Groups.

Click on Add Group. Fill in the Name and Group Description field and click on Add Group.

Next, we add a user to our Group. On the Groups page, select the just created group and click on Manage People.

The Members pane should be empty. Fix that by hovering over your current user on the Not Members pane and clicking the add button that shows up to the right. Doing that will move your user from the Not Members pane to the Members pane. When you are done adding people to the group, confirm the change by clicking on the Save button.

Now that we have a group, we want to assign that group to our Flask Okta application. In order to that, Click on Applications and then select the Assignments tab.

Click on the Assign button and select Assign to Groups. Then click on the Assign button beside Flask Okta group we just created. Click on Done to confirm your assignment.

Finally, remove the Everyone group assignment from our application by clicking the X button beside the assignment.

Go to the browser and try to hit the /greet endpoint. Sign in as the user you added to the group. You should find that you are able to get a valid response from the endpoint.

To test that all other users who try to access the /greet are unable to. You can either add a new user to the group: Flask Okta or you can remove your user account from the Flask Okta group.

To remove your user, simply click on the Flask Okta group on the Groups page and then the Manage People button on Flask Okta detail page. You should be able to remove your user from the group by hovering on your user and clicking the remove button that appears.

To add a new user, hover over the Users tab and select People. On the People page, click on the Add Person button. In the dialog box that pops up, fill in the details about the new users. Remember to enter Flask Okta as the group of the new user. Following this approach should both create a new user and add the user to the Flask Okta group.

And that brings us to the end of this tutorial. We see that setting a Flask application to use Okta’s OpenID connect is nothing short of a breeze. Services like Okta help us focus on the core functionality of our product while offloading the bulk of the authentication/authorization functions. You should think twice about leaving authentication to a third party service. Ensure it is an acceptable security risk for your application. In my case, it made extreme sense since that is what the client organization already uses to handle authentication on most of the applications that its employees interact with.

--

--

Erika Dike
The Andela Way

I write software and occasionally publish stuff about some things I found interesting.