Skip to main content

Implementing Cryptr authorization with Flask

Cryptr - Flask illustration

Flask Authorization Getting Started: Secure an API

Python 3.9.5 | Flask 2.0.015min

Learn how to use Cryptr to implement authorization with Flask

In this tutorial, youโ€™ll learn how to secure your BackEnd Flask API to receive your request from a user in a session on your Javascript app (such as React, Angular, Vue).

Thanks to the Cryptr Service that will check the access token attached to each request from your JS app, the backend will be able to legitimate the request. It knows whether it can provide or not a positive response with the resquested data.

You'll be building a simple API that only allows Cryptr users to access the list of courses. For this example, we'll use a Vue application to check if the token is valid with the Cryptr security procedure.

Let's get started! ๐Ÿ˜‰

1. Configurationโ€‹

Create a Flask projectโ€‹

๐Ÿ› ๏ธ๏ธ First, create a directory for your project and go to that directory

mkdir cryptr-flask-api-sample
cd cryptr-flask-api-sample

๐Ÿ› ๏ธ๏ธ Next, create the virtual environment with the virtual env command, and a name for your virtual environment. We can specify which version of Python we want to use. Here, it is Python 3.

virtualenv env -p python3

That creates a new directory called env, with all of the Python requirements to run that virtual environment.

๐Ÿ› ๏ธ๏ธ Activate the virtual environment, with the following command . env/bin/activate

Rendering

2. Application keysโ€‹

๐Ÿ› ๏ธ๏ธ In order to start, you need to create a free Cryptr account if you don't have one yet.

I already have a Cryptr account

๐Ÿ‘‹ If you already have a Cryptr account, you can go ahead and skip to the "Create api in my Cryptr application" steps

Rendering Rendering

๐Ÿ› ๏ธ๏ธ You can then create your application by following the onboarding steps. For this tutorial, choose a Vue application and a Python Flask api.

Rendering

Once you've completed all the funnel, after the processing steps, you arrive on the homepage of the back-office where you have access to various guides that will help you. They will depend on the choices of technology you have made in the funnel.

Rendering

Create api in my Cryptr applicationโ€‹

๐Ÿ› ๏ธ๏ธ If you want to create an API from your Cryptr account, all you have to do is click on API in the left menu

Rendering

๐Ÿ› ๏ธ Then click on the Backend APIs tab to switch and display the list of APIs.

Rendering

๐Ÿ› ๏ธ Then click on the "+ Add API" button on the top right. A modal will appear, where you can enter the name, type and description of the API. Click on the Save button.

Rendering

๐Ÿ› ๏ธ You have created a new API!

Rendering

3. Add your cryptr credentialsโ€‹

๐Ÿ› ๏ธ๏ธ Create your configuration file with the variables that you obtained previously (you can retrieve them in your Cryptr application). Don't forget to replace YOUR_DOMAIN with your own domain:

echo "CRYPTR_AUDIENCE='http://localhost:8081'
CRYPTR_BASE_URL='https://auth.cryptr.eu'
CRYPTR_TENANT_DOMAIN='YOUR_DOMAIN'" >> config.py
NOTE

If you are from the EU, you must add https://auth.cryptr.eu/ in the CRYPTR_BASE_URL variable, and if you are from the US, you must add https://auth.cryptr.us/ in the same variable.

4. Validate Access Tokenโ€‹

Install dependenciesโ€‹

๐Ÿ› ๏ธ๏ธ Create requirements.txt file

echo "flask                                                        
python-dotenv
python-jose
flask-cors
six" >> requirements.txt

This file lists all of the libraries that we will have installed in this virtual environment

๐Ÿ› ๏ธ๏ธ Install the requirements with pip install -r requirements.txt

Create a Flask applicationโ€‹

๐Ÿ› ๏ธ๏ธ Create the server.py file

touch server.py

๐Ÿ› ๏ธ๏ธ Open up the server.py file and initialize the server with the public route response of the courses by pasting in the following code:

server.py
"""Python Flask Cryptr Resource server integration sample
"""
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/v1/courses')
def index():
return jsonify([
{
"id": 1,
"user_id": "eba25511-afce-4c8e-8cab-f82822434648",
"title": "learn git",
"tags": ["colaborate", "git" ,"cli", "commit", "versionning"],
"img": "https://carlchenet.com/wp-content/uploads/2019/04/git-logo.png",
"desc": "Learn how to create, manage, fork, and collaborate on a project. Git stays a major part of all companies projects. Learning git is learning how to make your project better everyday",
"date": '5 Nov',
"timestamp": 1604577600000,
"teacher": {
"name": "Max",
"picture": "https://images.unsplash.com/photo-1558531304-a4773b7e3a9c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80"
}
}
])

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

๐Ÿ› ๏ธ๏ธ Integrate your cryptr config in server.py:

server.py
"""Python Flask Cryptr Resource server integration sample
"""
from flask import Flask, jsonify

app = Flask(__name__)

# Integrate your cryptr config:
app.config.from_object('config')

# ...

We can retrieve the response using a HTTP GET request on http://localhost:5000/api/v1/courses

๐Ÿ› ๏ธ๏ธ Before running the server, you need to tell your terminal which application to work with by exporting the FLASK_APP environment variable

export FLASK_APP=server.py

๐Ÿ› ๏ธ๏ธ Run the server with the command flask run and open insomnia or postman to make a GET request which should end with 200:

Rendering

Cryptr Config helpersโ€‹

๐Ÿ› ๏ธ๏ธ Create Cryptr config helper methods in the server.py file:

server.py
# Cryptr Config helpers
def issuer():
return app.config["CRYPTR_BASE_URL"] + "/t/" + app.config["CRYPTR_TENANT_DOMAIN"]

def jwks_uri():
return issuer() + "/.well-known"

Authorization checks methodsโ€‹

๐Ÿ› ๏ธ๏ธ Add library imports for authorization request methods in the server.py file:

server.py
"""Python Flask Cryptr Resource server integration sample
"""
from flask import Flask, request, jsonify, _request_ctx_stack
import json
from six.moves.urllib.request import urlopen
from functools import wraps
from flask_cors import cross_origin
from jose import jwt
# ...

๐Ÿ› ๏ธ๏ธ Add a decorator which verifies the token:

server.py
# ...

# Add Authorization Request methods:

# Error handler
class AuthError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code

@app.errorhandler(AuthError)
def handle_auth_error(ex):
response = jsonify(ex.error)
response.status_code = ex.status_code
return response

# Authorization request check
def requires_auth(f):
"""Retrieve token and validate it
"""
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header()
rsa_key = get_rsa_key(token)
validate_token(token, rsa_key)
return f(*args, **kwargs)
return decorated

def get_token_auth_header():
"""Obtains the Access Token from the Authorization Header
"""
auth = request.headers.get("Authorization", None)
if not auth:
raise AuthError({
"code": "authorization_header_missing",
"description": "Authorization header is required"
}, 401)

parts = auth.split()
if parts[0].lower() != "bearer" or len(parts) != 2:
raise AuthError({
"code": "invalid_header",
"description": "Wrong Bearer Authorization header syntax"
})
return parts[1]

def get_rsa_key(token):
jsonurl = urlopen(jwks_uri())
jwks = json.loads(jsonurl.read())
unverified_header = jwt.get_unverified_header(token)
rsa_key = {}
for key in jwks["keys"]:
if key["kid"] == unverified_header["kid"]:
rsa_key = key
return rsa_key

def validate_token(token, rsa_key):
if rsa_key:
try:
payload = jwt.decode(
token,
rsa_key,
algorithms=["RS256"],
audience=app.config["CRYPTR_AUDIENCE"],
issuer=issuer()
)
except jwt.ExpiredSignatureError as expiredErr:
raise AuthError({
"code": "token_expired",
"description": "token is expired"}, 401)
except jwt.JWTClaimsError as expiredErr:
raise AuthError({
"code": "invalid_claims",
"description": "incorrect claims, please check the audience and issuer"}, 401)
except Exception as err:
raise AuthError({
"code": "invalid_header",
"description": err}, 401)
_request_ctx_stack.top.current_user = payload
return
raise AuthError({
"code": "no_rsa_key",
"description": "Unable to find appropriate key"}, 401)

# ...

5. Protect API Endpointsโ€‹

Decorate index to add guardโ€‹

We use the decorators and methods defined above to protect API endpoints.

๐Ÿ› ๏ธ๏ธ Decorate request to handle authorization, CORS and preflight with these lines:

@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth

Condition to secure

If there is no @requires_auth, this will not be secured

server.py
# ...

@app.route('/api/v1/courses')

# Decorate request:
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth

def index():
return jsonify([
{
"id": 1,
"user_id": "eba25511-afce-4c8e-8cab-f82822434648",
"title": "learn git",
"tags": ["colaborate", "git" ,"cli", "commit", "versionning"],
"img": "https://carlchenet.com/wp-content/uploads/2019/04/git-logo.png",
"desc": "Learn how to create, manage, fork, and collaborate on a project. Git stays a major part of all companies projects. Learning git is learning how to make your project better everyday",
"date": '5 Nov',
"timestamp": 1604577600000,
"teacher": {
"name": "Max",
"picture": "https://images.unsplash.com/photo-1558531304-a4773b7e3a9c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80"
}
}
])

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

The @cross_origin tag helps to handle CORS and preflight request from the browser

Test with a Cryptr Vue appโ€‹

It is now time to try this on an application. For this purpose, we have an example app on Vue.

๐Ÿ›  Run your code with flask run

๐Ÿ› Clone our cryptr-vue-sample:

git clone --branch 07-backend-courses-api https://github.com/cryptr-examples/cryptr-vue2-sample.git

๐Ÿ›  Install the Vue project dependencies with yarn

๐Ÿ› ๏ธ๏ธ Now, let's retrieve your environment variables by visiting your Cryptr account. Click on ยซ Applications ยป in the left menu, then click on the application you will have created. A modal will appear and this is where you may copy/paste your environment variables.

Rendering Rendering

๐Ÿ› ๏ธ๏ธ Create the .env.local file and add your variables. Don't forget to replace YOUR_CLIENT_ID & YOUR_DOMAIN:

VUE_APP_AUDIENCE=http://localhost:8080
VUE_APP_CLIENT_ID=YOUR_CLIENT_ID
VUE_APP_DEFAULT_LOCALE=fr
VUE_APP_DEFAULT_REDIRECT_URI=http://localhost:8080
VUE_APP_TENANT_DOMAIN=YOUR_DOMAIN
VUE_APP_CRYPTR_TELEMETRY=FALSE

๐Ÿ› ๏ธ๏ธ Open up the Profile Component in src/views/Profile.vue and modify the url request:

src/views/Profile.vue
<script>
import { getCryptrClient } from "../CryptrPlugin";
export default {
data() {
return {
courses: [],
errors: [],
};
},
created() {
const client = getCryptrClient();
console.log("created");
client
.decoratedRequest({
method: "GET",
// url: "http://localhost/api/v1/courses
// Add port 5000 for flask:
url: "http://localhost:5000/api/v1/courses",
})
.then((data) => {
console.log(data);
this.courses = data.data;
})
.catch((error) => {
console.error(error);
this.errors = [error];
});
},
};
</script>

๐Ÿ› ๏ธ๏ธ Run the vue server with yarn serve and try to connect. Your Vue application redirects you to your sign form page, where you can sign in or sign up with an email.

SANDBOX EMAIL

You can log in with a sandbox email and we send you a magic link which should directly arrive in your personal inbox. Your sandbox email is based on your account's email. The email's structure is as follows: my-user-to-test@sandbox.my-admin-name. For example testemail@sandbox.lucas

Once you're connected, click on "Protected route". You can now view the list of the courses.

Itโ€™s done, congratulations if you made it to the end!

I hope this was helpful, and thanks for reading! ๐Ÿ™‚