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 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.

  • policy_id (required): Policy ID

  • policy_path_root (required): Policy root

  • authorizer (required): An Authorizer instance that describes the Authorizer service being used.

  • 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 import Identityfrom aserto_idp.auth0 import AccessTokenError, provide_identityfrom flask import request
    # Both `async` and non-`async` functions are supportedasync 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 supportedasync 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 supportedasync def custom_resource_context_provider():    """A naive implementation of the default resolver"""    return request.view_args

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 Flaskfrom flask.wrappers import Responsefrom 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.authorizeasync 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 Flaskfrom flask_aserto import AsertoMiddleware
app = Flask(__name__)aserto = AsertoMiddleware(**options)
# The `__displaystatemap` route is now definedaserto.register_display_state_map(app)
# The path name can be overriddenaserto.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 supportedasync 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 Flaskfrom flask.wrappers import Responsefrom 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.