Skip to main content

Protect your API on Python

note

Extract the Bearer Token from request headerโ€‹

"""requires-auth.py
"""
def get_auth_token():
auth = request.headers.get("Authorization", None)

if not auth:
raise Error({ code: 'auth.authorization_header_missing', status: 401 })

contents = auth.split()

if len(contents) < 2
raise Error({code: 'auth.authorization_token_invalid_format', status: 401})

elif contents[0] != 'Bearer'
raise Error({code: 'auth.authorization_token_type_not_supported', status: 401})

return contents[1]

Token validationโ€‹

For demonstration, we use the Flask app and jose package to create the require_auth decorator to validate the token's signature, expiration status, and required claims.

Install python-jose as your dependencyโ€‹

Pick the cryptography your are using in Logto. (ecdsa by default)

pip install python-jose[ecdsa]

Retrieve Logto's OIDC configurationsโ€‹

You will need a JWK public key set and the token issuer to verify the signature and source of the received JWS token. All the latest public Logto Authorization Configurations can be found at https://<your-logto-domain>/oidc/.well-known/openid-configuration.

e.g. Call https://logto.dev/oidc/.well-known/openid-configuration. And locate the following two fields in the response body:

{
"jwks_uri": "https://logto.dev/oidc/jwks",
"issuer": "https://logto.dev/oidc"
}

Create the authorization validation decoratorโ€‹

"""requires-auth.py
"""

import json
from flask import request, _request_ctx_stack
from six.moves.urllib.request import urlopen
from functools import wraps
from jose import jwt

def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header()

# jwks_uri endpoint retrieved from Logto
jwks_uri = urlopen('https://<your-logto-domain>/oidc/jwks')

# issuer retrieved from Logto
issuer = 'https://<your-logto-domain>/oidc'

jwks = json.loads(jwks_uri.read())

try:
payload = jwt.decode(
token,
jwks,
# The jwt encode algorithm retrieved along with jwks. ES384 by default
algorithms=jwt.get_unverified_header(token).get('alg'),
# The API's registered resource indicator in Logto
audience='<your request listener resource indicator>',
issuer=issuer,
options={
'verify_at_hash': False
}
)
except Exception:
# exception handler
raise Error({code: 'invalid_token', status: 401})

# Custom code to process payload
_request_ctx_stack.top.user_id = payload.get('sub')

return f(*args, **kwargs)
return decorated

note

For ๐Ÿ” RBAC, scope validation is also required.

Apply decorator to your APIโ€‹

from flask import Flask
from flask_cors import cross_origin

APP = Flask(__name__)

@APP.route("/user/info")
@cross_origin(headers=["Content-Type", "Authorization"])
@requires_auth
def api:
# Your API Logic