I borrowed a lot from https://developer.okta.com/blog/2020/12/17/build-and-secure-an-api-in-python-with-fastapi thanks to Karl Hughes for this article.
Litestar framework for Python 3.x creating APIs.
For more information about Litestar visit https://litestar.dev/
OKTA Setup
Step 1
After creating a developer account with okta click 'Applications' then 'Create App Integration' as shown in the image
Step 2
Choose 'API Services' with a description of
'Interact with Okta APIs using the scoped OAuth 2.0 access tokens for machine-to-machine authentication.'
Step 3
Give the integration a name
Result Screen
Step 4
In the directory for the Python project create a file named .env with the following:
OKTA_CLIENT_ID=Value From The Previous Step
OKTA_CLIENT_SECRET=Value From The Previous Step
OKTA_ISSUER="{oktaDomain}/oauth2/default"
OKTA_TOKEN="{oktaDomain}/oauth2/default/v1/token"
OKTA_INTROSPECTION="{oktaDomain}/oauth2/default/v1/introspect"
OKTA_AUDIENCE="api://default"
OKTA_SCOPE="items"
Step 5
Now go to 'Security' -> 'API' shown below:
Note that values for Audience and Issuer URI are in .env file.
Step 6
Click on the edit icon you should see something like this:
Step 7
Try the 'Metadata URI' in a new tab in you web browser to see something like:
Step 8
Step 9
Add a scope such as what is shown below:
Step 10
Complete the .env file with the correct information including replacing {oktaDomain}
with your okta domain and having the correct value for OKTA_SCOPE="items"
, if you changed yours that is and note that OKTA_SCOPE is important for remote validation.
Python Code
You can find the final code for this here https://github.com/pbaletkeman/litestarOKTA.
Large segments of this code came from https://docs.litestar.dev/latest/usage/security/jwt.html.
To learn more about Litestar security go here https://docs.litestar.dev/latest/usage/security/index.html
Download the files to your local system and copy them to your Python project directory.
Step 11
Install the required Python libraries using
pip install -r requirements.txt
Note, that this includes all of litestar[full] which may be more than you need.
Step 12
Libraries of note:
-
base64
is used to encode yourclient id
andclient secret
-
httpx
is used to connect to the OKTA server and return the JWT token -
okta_jwt.jwt
is used to validate the JWT token locally, which is quicker, but less secure than JWT token validation on the OKTA server -
starlette.config
is used to import the .env file into your application, you may want to usepython-dotenv
instead
Step 13
In this sample project we only ever will have one user/account which means that we can do this
API_USER_DB: dict[str, OAuthSchema] = {}
but if you have more than one account you should rethink how your database of users is stored.
Step 14
This project includes a method for local JWT token validation, but it is not used, the remote JWT token validate is used instead.
Remote validation is more secure, takes longer and increases system resource usage as this makes a call to the Introspection endpoint.
The local validation method is def validate(token: str) -> bool:
and the remote validation method is def validate_remote(token: str) -> bool:
Notes
Note 1
When running this project you should have the following endpoints:
- /login
- uses Authorization header to login
- /form-login
- uses data from HTML Form to login
- /json-login
- uses data from JSON body to login
as shown below:
Each endpoint has it's purpose and it's up to you to choose the appropriate one.
Note 2
In json_login_handler
and form_login_handler
there is some funky magic happening here:
auth_header = 'Basic ' + str(base64.b64encode((data.client_id + ':' + data.client_secret).encode('ascii')))[2:-1]
To get the token we must send a authorization header of 'basic' a base 64 encoded version of the username:password. However Python prepends the letter "b" and then surrounds the value with a single quote which is why we are using [2:-1]
Top comments (0)