Flask Middleware
Python 3 and mypy-compatible middleware for the Flask web framework.
Overview
This package provides three capabilities for Flask apps:
authorize
: route decorator that validates incoming requests and authorizes access to a route.register_display_state_map
: application middleware which implements and exposes an endpoint for returning the display state map for a service, based on its authorization policy.check
: function that can be called to make a decision about a user's access to a resource based on a policy.
Installation
Using pip:
pip install -U flask-aserto
Using Poetry:
poetry add flask-aserto
Middleware instantiation
To use any of the capabilities, you must first create an AsertoMiddleware
instance.
from flask_aserto import AsertoMiddleware
aserto = AsertoMiddleware(**options)
Options
All options must be passed as keyword arguments, but may be provided in any order.
-
authorizer_options
(required): AnAuthorizerOptions
instance that describes the Authorizer service being used. -
policy_path_root
(required): Policy root -
identity_provider
(required): A callable object which returns anIdentity
instance that represents a user.Example using the aserto-idp package to define an Auth0 identity provider
NoteThe
aserto-idp
package needs to be installed separately.from aserto.client import Identity
from aserto_idp.auth0 import AccessTokenError, provide_identity
from flask import request
# Both `async` and non-`async` functions are supported
async def identity_provider() -> Identity:
authorization_header = request.headers.get("Authorization")
if authorization_header is None:
# Represents an "anonymous"/"logged-out" user
return Identity(type="NONE")
try:
identity = await provide_identity(
authorization_header=authorization_header,
domain=AUTH0_DOMAIN,
client_id=AUTH0_CLIENT_ID,
audience=AUTH0_AUDIENCE,
)
except AccessTokenError:
return Identity(type="NONE")
# Represents a user from an Auth0 user directory
return Identity(type="SUBJECT", subject=identity) -
policy_path_resolver
(optional): By convention, Aserto policy package names are of the formpolicy_root.METHOD.path
. By default, the package name will be inferred from the policy name, HTTP method, and route path:-
A
GET
request to/api/users
would use the policy package namedpolicy_root.GET.api.users
-
A
POST
request to/api/users/<id>
would use the policy package namedpolicy_root.POST.api.users.__id
The
policy_path_resolver
option can be used to override the default behavior. It must be a callable object which takes no arguments and returns astr
as the package name. The globalrequest
object provided byFlask
can be used to access the current route's request data if needed.Example:
from flask import request
POLICY_ROOT = "my_app_policy"
# Both `async` and non-`async` functions are supported
async def custom_policy_path_resolver():
"""A naive implementation of the default resolver"""
rule_string = str(request.url_rule) # e.g. "/api/users/<id>"
policy_sub_path = rule_string.replace("/", ".").replace("<", "__").replace(">", "")
return ".".join([POLICY_ROOT, request.method.upper(), policy_sub_path])
-
-
resource_context_provider
(optional): By default, the resource context data provided to Aserto authorizer calls will be created from the path parameters of the request. For example, if the route path is defined as/api/users/<id>
and a request is made to/api/users/42
, then the resource would be{"id": "42"}
.The
resource_context_provider
option can be used to override the default behavior. It must be a callable object which takes no arguments and returns adict
. The globalrequest
object provided byFlask
can be used to access the current route's request data if needed.Example:
from flask import request
# Both `async` and non-`async` functions are supported
async def custom_resource_context_provider():
"""A naive implementation of the default resolver"""
return request.view_args -
policy_instance_name
(optional): The name of the policy instance to target when calling a hosted authorizer. -
policy_instance_label
(optional): The label of the policy instance to target when calling a hosted authorizer.
authorize
decorator
Once you have an AsertoMiddleware
instance, you can use it to decorate routes so that the
permission policies will automatically determine whether the call to the endpoint is allowed.
Example
from flask import Flask
from flask.wrappers import Response
from flask_aserto import AsertoMiddleware, AuthorizationError
app = Flask(__name__)
aserto = AsertoMiddleware(**options)
@app.errorhandler(AuthorizationError)
def handle_auth_error(exception: AuthorizationError) -> Response:
"""
An `AuthorizationError` will be raised if the call to the endpoint is not allowed.
These can be automatically handled by returning a 403 (Forbidden) status code.
"""
return Response(response=f"Forbidden by policy {exception.policy_path}", status=403)
@app.route("/api/users", methods=["GET"])
@aserto.authorize
async def api_users() -> Response:
...
# The options provided to the `AsertoMiddleware` can also be overridden per route
@app.route("/api/users/<id>", methods=["POST"])
@aserto.authorize(**option_overrides)
async def api_user(id: str) -> Response:
...
register_display_state_map
The endpoint defined by the register_display_state_map
middleware is how the Flask SDK exposes the
display state map pattern to front-ends (e.g. the
React SDK and
JavaScript SPA SDK).
Use the register_display_state_map
middleware to set up an endpoint that returns the display state
map to a caller. The endpoint is named __displaystatemap
by default, but can be overridden in options.
Example
from flask import Flask
from flask_aserto import AsertoMiddleware
app = Flask(__name__)
aserto = AsertoMiddleware(**options)
# The `__displaystatemap` route is now defined
aserto.register_display_state_map(app)
# The path name can be overridden
aserto.register_display_state_map(app, endpoint="custom_display_state_map_path")
Options
-
endpoint
(optional): Overrides the default endpoint name -
resource_context_provider
(_optional): By default, the resource context will be thePOST
body of the request.The
resource_context_provider
option can be used to override the default behavior. It must be a callable object which takes no arguments and returns adict
. The globalrequest
object provided byFlask
can be used to access the route's request data if needed.Example:
from flask import request
# Both `async` and non-`async` functions are supported
async def custom_resource_context_provider():
return request.get_json(silent=True) or {}
check
function
An alternative to the authorize
decorator is the check
function, which provides an explicit
mechanism for calling the Aserto authorizer.
Use the check
function to call the authorizer with a decision, policy, and resource, and get a boolean True
or False
response.
The decision is a named value in the policy: the string allowed
is used by convention.
Examples: check("allowed")
, check("enabled")
, check("visible")
, etc.
Example
from flask import Flask
from flask.wrappers import Response
from flask_aserto import AsertoMiddleware
app = Flask(__name__)
aserto = AsertoMiddleware(**options)
@app.route("/api/users", methods=["GET"])
async def api_users() -> Response:
if not await aserto.check("allowed", **option_overrides):
return Response(status=403)
...
Github
This package is open source and can be found on GitHub.