File: guards.rst

package info (click to toggle)
litestar 2.21.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 12,568 kB
  • sloc: python: 70,588; makefile: 254; javascript: 104; sh: 60
file content (83 lines) | stat: -rw-r--r-- 3,991 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Guards
======

Guards are :term:`callables <python:callable>` that receive two arguments - ``connection``, which is the :class:`Request <.connection.Request>` or :class:`WebSocket <.connection.WebSocket>` instance (both sub-classes of :class:`~.connection.ASGIConnection`), and ``route_handler``, which is a copy of the
:class:`~.handlers.BaseRouteHandler`. Their role is to *authorize* the request by verifying that
the connection is allowed to reach the endpoint handler in question. If verification fails, the guard should raise an
:exc:`HTTPException`, usually a :class:`~.exceptions.NotAuthorizedException` with a
``status_code`` of ``401``.

To illustrate this we will implement a rudimentary role based authorization system in our Litestar app. As we have done
for ``authentication``, we will assume that we added some sort of persistence layer without actually specifying it in
the example.

We begin by creating an :class:`~enum.Enum` with two roles - ``consumer`` and ``admin``:

.. literalinclude:: /examples/security/guards.py
    :language: python
    :lines: 12-14
    :caption: Defining the enum ``UserRole``

Our ``User`` model will now look like this:

.. literalinclude:: /examples/security/guards.py
        :language: python
        :lines: 17-24
        :caption: User model for role based authorization

Given that the ``User`` model has a ``role`` property we can use it to authorize a request.
Let us create a guard that only allows admin users to access certain route handlers and then add it to a route
handler function:

.. literalinclude:: /examples/security/guards.py
        :language: python
        :lines: 27-29, 32-33
        :caption: Defining the guard ``admin_user_guard`` used to authorize certain route handlers

Here, the ``admin_user_guard`` guard checks if the user is an admin.

The connection has a `user` object attached to it thanks to the JWT middleware, see :doc:`authentication </usage/security/jwt>`
and in particular the :meth:`JWTAuth.retrieve_user_handler` method.

Thus, only an admin user would be able to send a post request to the ``create_user`` handler.

Guard scopes
------------

Guards are part of Litestar's :ref:`layered architecture <usage/applications:layered architecture>` and can be declared
on all layers of the app - the Litestar instance, routers, controllers, and individual route handlers:

.. literalinclude:: /examples/security/guards.py
    :language: python
    :lines: 36-49
    :caption: Declaring guards on different layers of the app

The placement of guards within the Litestar application depends on the scope and level of access control needed:

- Should restrictions apply to individual route handlers?
- Is the access control intended for all actions within a controller?
- Are you aiming to secure all routes managed by a specific router?
- Or do you need to enforce access control across the entire application?

As you can see in the above examples - ``guards`` is a :class:`list`. This means you can add **multiple** guards at
every layer. Unlike :doc:`dependencies </usage/dependency-injection>` , guards do not override each other but are
rather *cumulative*. This means that you can define guards on different layers of your app, and they will combine.

.. caution::

    If guards are placed at the controller or the app level, they **will** be executed on all ``OPTIONS`` requests as well.
    For more details, including a workaround, refer https://github.com/litestar-org/litestar/issues/2314.


The route handler "opt" key
---------------------------

Occasionally there might be a need to set some values on the route handler itself - these can be permissions, or some
other flag. This can be achieved with :ref:`the opts kwarg <handler_opts>` of route handler

To illustrate this let us say we want to have an endpoint that is guarded by a "secret" token, to which end we create
the following guard:

.. literalinclude:: /examples/security/guards.py
    :language: python
    :lines: 52-61