Skip to main content

Flask Middleware

Python 3 and mypy-compatible middleware for the Flask web framework.

Overview

This package provides three capabilities for Flask apps:

  1. authorize: route decorator that validates incoming requests and authorizes access to a route.
  2. 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.
  3. 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): An AuthorizerOptions instance that describes the Authorizer service being used.

  • policy_path_root (required): Policy root

  • identity_provider (required): A callable object which returns an Identity instance that represents a user.

    Example using the aserto-idp package to define an Auth0 identity provider

    Note

    The 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 form policy_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 named policy_root.GET.api.users

    • A POST request to /api/users/<id> would use the policy package named policy_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 a str as the package name. The global request object provided by Flask 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 a dict. The global request object provided by Flask 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 the POST 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 a dict. The global request object provided by Flask 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.