Every ModelAdmin now emits an ECS audit event when an object is created, changed or deleted, and when a bulk action runs. Actions are templated as admin.<target>.<verb>, after the model name. A signed-in account without staff access reaching the admin is recorded as a denied admin.access. The wiring is a custom AdminSite installed through AdminConfig.default_site: it mixes the auditing into every admin class at registration, including the ones Django declares, so a new ModelAdmin is covered without doing anything. It hangs off log_addition, log_change, log_deletions and get_actions, which Django calls in every path, rather than off save_model. Deletions noted by log_deletions are emitted from delete_model and delete_queryset, once their outcome is known. Changed field names are always reported; their values only for the admin_values each model is registered with, and never for anything that looks like a secret. An account acted upon is reported as user.target: the target itself when it is a user, else the account its model registers as user_target, as the user an access grants a role to. The emitter derives it for every event, so user.provision names the user it creates as well.
30 KiB
Audit logging
La Suite Meet keeps an audit log: a record of the notable things that happen in the product, such as a room created through the external API, a sign-in, or a change made in the Django admin. Every entry answers the same questions: who did what, to what, on whose behalf, from where, and did it work?
It is written for:
- operators and security teams investigating an incident or watching for abuse;
- data protection officers, who need to know who accessed or changed what;
- developers adding a new audited action (see For developers).
Each event is a single line of JSON written to standard output, kept apart from the application logs so that your log collector can route it to its own index. Events follow the Elastic Common Schema (ECS) 9.5.0, which most SIEMs and Elasticsearch understand out of the box.
Personal data is kept to a minimum. A person is identified by their account id, their OIDC sub and the domain of their
email address, never by the address itself. Secrets and the messages of unexpected exceptions are never recorded.
What an event looks like
Events are written on the dedicated audit logger, one per line. Pretty-printed, one looks like this:
{
"@timestamp": "2026-09-15T08:41:12.345+00:00",
"ecs": {"version": "9.5.0"},
"data_stream": {"type": "logs", "dataset": "meet.audit", "namespace": "default"},
"service": {"name": "meet", "environment": "production", "version": "1.34.0", "node": {"name": "meet-backend-6d8f7b9c4-x2x7q"}},
"event": {
"kind": "event",
"id": "0b3f6a0e-5d1c-4f43-9a8e-2b7c1d0e4f5a",
"action": "room.create",
"category": ["api"],
"type": ["creation"],
"outcome": "success",
"dataset": "meet.audit"
},
"client": {"ip": "1.2.3.4"},
"source": {"ip": "1.2.3.4"},
"http": {"request": {"id": "6f1c0d0e-2a8b-4c1d-9e7f-0a1b2c3d4e5f", "method": "POST"}, "response": {"status_code": 201}},
"url": {"path": "/external-api/v1.0/rooms/"},
"user_agent": {"original": "calendar-app/2.3"},
"user": {"id": "beecd833-4be4-4675-b139-a196b07144a9", "domain": "gouv.fr"},
"organization": {"id": "calendar-app"},
"entity": {
"target": {
"id": "9ae54744-ae64-44f4-b094-71e137556b66",
"sub_type": "room",
"name": "Daily standup",
"raw": {"slug": "daily-standup", "access_level": "trusted"}
}
},
"lasuite": {
"actor": {"type": "application", "sub": "0edebfa3-1355-4891-ae63-daf9fd37ac04"},
"auth": {"method": "application_jwt"},
"application": {"client_id": "calendar-app"},
"outcome": "success"
},
"log": {"level": "info", "logger": "audit"}
}
Read it as: the application calendar-app, acting on behalf of a user from gouv.fr, created the room "Daily standup"
from 1.2.3.4, and it worked.
When an action is refused, the event keeps the name of the action that was attempted, here still room.create. Only
the fields describing the outcome change:
{
"event": {"action": "room.create", "outcome": "failure", "type": ["creation", "denied"], "reason": "permission_denied"},
"lasuite": {"outcome": "denied"},
"http": {"response": {"status_code": 403}},
"error": {"message": "..."},
"log": {"level": "warning", "logger": "audit"}
}
Key concepts
These words are used throughout this page and in the code (core/audit/). The In the event column says where to
find each one in the JSON above.
The record
| Term | Meaning | In the event |
|---|---|---|
| Audit Event | One immutable record that a notable action was attempted, by whom, on what, and with what outcome. Written as a single JSON line on the Audit Log. | The whole document, identified by event.id |
| Audit Log | The stream of Audit Events. It is distinct from the application logs: a logger and a dataset of its own. | data_stream.*, event.dataset |
| Action | The dotted, stable name of what was attempted, such as room.create or application.token.issue. It names the attempt, not its result: a refused room.create is still a room.create. Every Action belongs to the Catalogue. |
event.action |
| Catalogue | The closed list of Actions the code may emit, with their meaning and fields: see Catalogue. Writes made through the Django admin are its one templated family, admin.<target>.<verb>. |
— |
| Details | The fields specific to one Action, such as the scopes granted with a token or the fields a room update changed. The Catalogue lists them. | lasuite.details.* |
Who acted
| Term | Meaning | In the event |
|---|---|---|
| Actor | Who is behind an Action. It always has an Actor Type. When a person's account is involved, the event also carries the account id, its OIDC sub when it has one, and its email domain. |
lasuite.actor.*, user.* |
| Actor Type | What kind of Actor it is: user, application, service, system or anonymous. See Actors. Device accounts are users like any other account. |
lasuite.actor.type |
| Auth Method | How the Actor proved who it is for this request: session, oidc, password, application_jwt, client_credentials, addons_jwt, resource_server, livekit_token, shared_secret, none when it did not authenticate, or unknown. |
lasuite.auth.method |
| Application | A registered third-party integration, identified by its client id, that calls the external API on behalf of users. | lasuite.application.client_id |
| Delegation | An Application acting for one specific user, through the delegated token it obtained with application.token.issue. The event then names both: the Application as the Actor, and the user in user.*. |
lasuite.actor.type = application, with user.* set |
| Provisional User | An account an Application created from an email address before that person ever signed in. It has no OIDC sub yet, so its account id is what links its user.provision event to its later user.login. |
event.action = user.provision |
| Machine User | The identity of an internal service calling the backend with a shared secret: the LiveKit SIP bridge (roomkit) and the summary service (summary). It is an Actor of type service, never an account. |
service.origin.name |
| Tenant | The organisation an event belongs to: the calling Application when there is one, otherwise the Actor's email domain. | organization.id |
What it was done to
| Term | Meaning | In the event |
|---|---|---|
| Target | The resource the Action was attempted on: a room, a recording, an access grant, an application or a user. Described by its type, its id and a few stable fields. See Targets. | entity.target.* |
| User Target | The person the Action was about, when there is one: a provisioned user, the user an access grants a role to, or the account an admin edits. Not to be confused with user.*, the account whose authority was used. |
user.target.* |
How it went
| Term | Meaning | In the event |
|---|---|---|
| Outcome | Whether the Action succeeded (success), failed (failure), was refused (denied), or ended in a way the backend could not interpret (unknown). ECS has no denied: in event.outcome, a refusal is a failure. |
lasuite.outcome, event.outcome |
| Reason | Why an Action did not succeed, from a closed list: authentication_failed, permission_denied, rate_limited, validation_error, not_found, conflict, internal_error. |
event.reason |
| Refusal | An Action the backend would not let happen, such as a 401, 403 or 429. It is recorded under the Action that was attempted, with Outcome denied, never as an Action of its own. |
lasuite.outcome = denied |
Where and when
| Term | Meaning | In the event |
|---|---|---|
| Source | Where the request came from: the real client IP (never a proxy's), the HTTP method, the URL path and the user agent. Empty when no request is behind the event. | client.ip, source.ip, http.request.method, url.path, user_agent.original |
| Request Id | The id shared by the Audit Events, the application logs and the web server access log of one HTTP request, what other tools call a trace id. The backend generates it, unless the ingress is trusted to set it. | http.request.id |
| Emitter | The backend instance that wrote the event: its name, environment, release and host (the pod, on Kubernetes). Never the caller. | service.name, service.environment, service.version, service.node.name |
| Peer Service | Another service of the deployment taking part in the Action: the one that called the backend, or the one the backend called. | service.origin.name, service.target.name |
Where events go
| Term | Meaning | In the event |
|---|---|---|
| Sink | Where Audit Events are written: the audit logger, whose handler writes to AUDIT_LOG_STREAM, standard output by default. A log shipper takes them from there. |
log.logger = audit |
| Data Stream | The data stream events are meant for, logs-meet.audit-default by default. It is what lets a shipper route audit events away from the application logs. |
data_stream.* |
Answering common questions
These are the fields to filter on for the questions that come up most. They work the same whatever tool you search your logs with.
Who created, changed or deleted this room?
Filter on entity.target.sub_type: room with the room's id in entity.target.id, or its slug in
entity.target.raw.slug. event.action says what happened, and user.id or lasuite.application.client_id who did
it. Rooms are audited when they are handled through the external API (room.*) or the Django admin (admin.room.*).
What has this application done?
Filter on lasuite.application.client_id. To include the attempts made before its credentials were verified, such as a
wrong secret or an inactive application, also search lasuite.details.claimed_client_id.
What did this person do, and what was done to their account?
user.id is the account whose authority an action used, and user.target.id the account an action was about: search
both. To follow someone across the other La Suite products, use their OIDC sub, in lasuite.actor.sub.
Which requests were refused, and why?
Filter on lasuite.outcome: denied and group by event.reason. A burst of authentication_failed on user.login or
application.token.issue from a single source.ip deserves a closer look.
What else happened during this request?
Take its http.request.id. The same id is sent back in the X-Request-ID response header, appears on every audit event
and application log line of the request, and in the Gunicorn access log as rid=. A single request may produce several
events: an application obtaining a token for someone who never signed in produces both user.provision and
application.token.issue.
Fields reference
Events follow the Elastic Common Schema 9.5.0. Only two namespaces hold
what ECS does not define: lasuite.*, and entity.target.raw.* for the fields of a target.
| Field | Meaning |
|---|---|
@timestamp |
When the event was emitted: ISO 8601, in UTC, with millisecond precision |
data_stream.*, event.dataset |
logs, <AUDIT_LOG_SERVICE_NAME>.audit and AUDIT_LOG_DATA_STREAM_NAMESPACE: what sets the audit stream apart from the application logs |
service.name, service.environment, service.version, service.node.name |
The Emitter: AUDIT_LOG_SERVICE_NAME, the current environment, the release and the host name (the pod, on Kubernetes). Never the caller |
service.origin.name |
The internal service that called the backend, when the actor is a service: roomkit or summary |
service.target.name |
The service the backend called, when one is named |
event.id |
Unique id of the event, so that a shipper retrying it cannot create a duplicate |
event.action |
What was attempted, from the Catalogue |
event.category, event.type |
The ECS classification (api, authentication, iam... and creation, change, access, denied, user...). Always a combination ECS expects, see Classification |
event.outcome |
ECS success or failure (refusals included), or unknown when the result was reported in terms the backend does not recognise |
event.reason |
Why it did not succeed: authentication_failed, permission_denied, rate_limited, validation_error, not_found, conflict, internal_error |
lasuite.outcome |
success, failure, denied or unknown: the same as event.outcome, with refusals kept apart |
lasuite.actor.type |
user, application, service, system or anonymous, see Actors |
lasuite.actor.sub |
The OIDC sub of the account in user.*, when it has one: the identity it shares with the other La Suite products |
lasuite.auth.method |
session, application_jwt, addons_jwt, resource_server, livekit_token, shared_secret, client_credentials, oidc, password, none, or unknown for an authentication class that is not registered. Requests served outside DRF, such as the admin and logout, report session when signed in |
lasuite.application.client_id |
The external application acting, if any. Only set once its credentials are verified |
user.id, user.domain |
The account whose authority the action used (see Actors): its primary key and email domain. The email address itself is never recorded |
user.roles |
That account's privileges when the event happened: superuser, staff |
user.target.id, user.target.domain |
The account the action was about: the target when it is a user, or the user an access grants a role to. user.* stays the actor |
organization.id |
The Tenant: the application's client id when there is one, otherwise the user's email domain |
entity.target.* |
The resource acted on, see Targets |
lasuite.details |
Fields specific to the action, see the Catalogue |
client.ip, source.ip |
The real client address, the same one DRF's throttles identify (see NUM_PROXIES) |
http.request.method, url.path |
The request as received |
user_agent.original |
The client's User-Agent, cut at 1024 characters |
http.request.id |
The Request Id. Also sent back as the X-Request-ID response header and logged by Gunicorn as rid=. Generated by the backend unless REQUEST_ID_TRUST_HEADER is set |
http.response.status_code |
The status of the response, for API calls and refusals |
error.message |
A human-readable reason for a failure |
error.type |
The class of an unhandled exception. Its message is left out, as it may contain personal data |
log.level |
info for a success, warning for failures, refusals and unknown outcomes, error for internal errors |
Targets
The resource an action was performed on is described with ECS entity.target, the field set ECS 9.3 introduced for
"the targeted entity of an action taken". ECS still flags the entity fields as beta.
| Field | Meaning |
|---|---|
entity.target.id |
Its primary key |
entity.target.sub_type |
Its model: room, recording, user, application, resourceaccess... |
entity.target.type |
Its ECS entity type, when one fits: user for an account, application for an application |
entity.target.name |
Its name, when its model registers one |
entity.target.raw.* |
The other fields its model registers, such as a room's slug and access_level, or the OIDC sub of a user |
Actors
lasuite.actor.type says who acted, and user.* whose authority the action used. They are not always the same: when
an application acts for a user, the actor is the application, but user.* is that user.
lasuite.actor.type |
Who | user.* |
|---|---|---|
user |
A person's account acting for itself: through a session, an OIDC or password login, an add-on token, or the LiveKit token of a known account | That account |
application |
A client application acting on behalf of a user. Either a Meet application, through its client credentials or its delegated token, or another La Suite application, through the resource server. lasuite.application.client_id names it |
The delegating user |
service |
An internal service of the deployment, named by service.origin.name: the LiveKit SIP bridge (roomkit) or the summary service (summary) |
Absent |
system |
The backend itself, with no inbound request | Absent |
anonymous |
A caller that did not authenticate, or failed to | Absent |
The backend picks the actor type by applying these rules in order:
- A client id is known, usually from the token payload:
application. - There is neither a request nor an actor:
system. - The caller authenticated without an account, as a Machine User:
service. - The caller is an account:
user. - Anything else:
anonymous.
Classification
event.category and event.type are only ever combined in the ways the ECS 9.5.0 expected_event_types allow:
- Each action declares its category,
apiby default, and its types. A type its category does not expect fails at import time, so a misclassified action can't ship. - A refusal adds the
deniedtype only when the category expects it. An API call becomes["creation", "denied"]. A failed login stays["start"], andevent.outcome,event.reasonandlasuite.outcomeshow that it was refused. - A 401 is also filed under
authentication, as in["api", "authentication"], so that it counts as a failed authentication.
When an audited API action raises an exception DRF does not handle, the event is still recorded before the exception
propagates: as a failure with reason internal_error, status code 500 and error.type.
Catalogue
Action names are dotted and lower-case, in the form <target>.<verb>. They name what was attempted: whether it worked
is told by event.outcome, lasuite.outcome and event.reason, never by the name.
Applications and delegation
event.action |
Emitted when | Notable fields |
|---|---|---|
application.token.issue |
An application asks for a delegated token (POST /external-api/v1.0/application/token/), whether it gets one or is refused. It can be refused for bad credentials, an inactive application, an invalid or unauthorized email domain, an unknown user, or a provisioning conflict |
On success: user.* is the delegated user, entity.target the application, and lasuite.details holds scopes, user_provisioned and expires_in. On refusal: event.reason, http.response.status_code and lasuite.details.requested_domain. Until its credentials are verified, the client id the caller submitted is only recorded as lasuite.details.claimed_client_id: it never sets lasuite.application or organization |
user.provision |
An application creates a provisional user from an email address, or fails to. It is a failure with reason conflict when a concurrent request created the user first |
user.target and entity.target are the user, the existing one on a conflict. A provisional user has no sub yet: its id links this event to the winning one and to its later user.login |
Rooms
Rooms are audited on the external API only.
event.action |
Emitted when | Notable fields |
|---|---|---|
room.create |
A room is created through the external API, or the attempt fails | entity.target is the room |
room.update |
A room is updated through the external API, or the attempt fails | entity.target is the room, refusals included. lasuite.details.updated_fields and previous_access_level |
room.retrieve |
A room is read through the external API, or the attempt fails | entity.target is the room |
room.list |
Rooms are listed through the external API, or the attempt fails | lasuite.details.total |
Sign-in and the admin
event.action |
Emitted when | Notable fields |
|---|---|---|
user.login |
A user signs in, or a sign-in attempt fails: denied, with reason authentication_failed |
lasuite.auth.method is oidc, password or unknown. On success, it is named after the login backend, also given in lasuite.details.auth_backend. On failure, it is named after the credentials submitted: a password, or the nonce of the OIDC callback |
user.logout |
A user signs out | |
admin.access |
A signed-in account without staff access reaches an admin page. Always denied, once per refused page | event.category is web and event.type is access. event.reason, and http.response.status_code: the redirect to the login page |
admin.<target>.<verb> |
A write is made through the Django admin, see Django admin |
Django admin
The admin is the most sensitive part of the product, so every write made through it produces an audit event, alongside
the LogEntry Django already writes. Nothing is replaced and there is no extra table: the admin history keeps working.
The action name follows a template, admin.<target>.<verb>, where <target> is the model name and <verb> one of:
<verb> |
Emitted when | Notable fields |
|---|---|---|
create |
An object is added | lasuite.details.changed_fields, changes |
update |
An object is changed | lasuite.details.changed_fields, changes |
delete |
An object is deleted: one event per object, once the deletion has run. A deletion that raises is a failure with reason internal_error, and in a bulk deletion every selected object is then reported as failed |
error.message on failure |
action |
A bulk action runs | lasuite.details.admin_action, count |
This gives names such as admin.room.update, admin.user.delete or admin.recording.action. event.category is iam
for anything that grants access to the product, and configuration otherwise. Writes on a user or a group put user or
group first in event.type, as in ["user", "change"].
What changed
lasuite.details.changed_fieldsalways lists the names of the fields a form changed, exactly the ones Django reports in its own history.lasuite.details.changesgives their values, as{"from": ..., "to": ...}, but only for the fields a model explicitly allows in theadmin_valuesit is registered with (see Registering models).- Anything that looks like a secret is refused, whatever the allow-list says. A password change is reported as a change
to
password, never with its value. - A
JSONFieldon the allow-list, such as a room'sconfiguration, is recorded as JSON rather than as a string, and both versions are kept whole.
Inlines
Objects edited through an inline produce their own event, linked to the parent's by http.request.id. Granting a
role on a room produces both admin.room.update and admin.resourceaccess.create, whose user.target is the account
granted the role.
What is not covered
- Reads. Opening a change list, a change form or the history page emits nothing. Django's own
LogEntryremains the record of who touched what. - Custom actions that bypass the ORM hooks. An action calling
queryset.update()orqueryset.delete()directly is reported as a singleadmin.<target>.action, with its name and the number of objects, not one event per object.delete_selectedis the exception: Django reports its objects throughlog_deletions, so it emits oneadmin.<target>.deleteper object and noactionevent.
How it is wired
The wiring lives in core/audit/admin.py. AuditedAdminSite adds auditing to every admin class when it is registered,
including those Django declares itself, and is installed through core.audit.apps.AuditedAdminConfig in
INSTALLED_APPS. A new ModelAdmin is therefore covered without doing anything. Registering its model (see
Registering models) only adds its category and its allowed values.
For developers: emitting events
Declaring an action
Every action is declared once, in core/auditing.py, as an audit.Action constant. An action may carry its ECS
category and types, which then apply to every event it emits:
APPLICATION_TOKEN_ISSUE = audit.Action(
"application.token.issue",
category=EventCategory.AUTHENTICATION,
types=(EventType.START,),
)
ROOM_CREATE = audit.Action("room.create")
Auditing a DRF view
A DRF view only declares the actions it audits: everything else is derived from the response. CRUD actions are mapped
in audit_actions. An extra action names its own on its route, so that renaming its method cannot silently stop
auditing it:
from core import audit, auditing
class RoomViewSet(audit.AuditViewMixin, viewsets.GenericViewSet):
audit_actions = {"create": auditing.ROOM_CREATE, "retrieve": auditing.ROOM_RETRIEVE}
def perform_create(self, serializer):
self.audit_target = serializer.save()
@action(detail=True, methods=["post"], audit_action=auditing.ROOM_INVITE)
def invite(self, request, pk=None): ...
AuditViewMixin fills in the rest:
- It runs from DRF's
finalize_responsehook, which every response goes through, successful or not. - The ECS category and types come from the action, or default to
apiand the DRF action. - The outcome, reason and status code come from the response status: 401, 403 and 429 are
denied, other errors arefailure, and a 401 is always filed underauthentication. - The target is the object
get_object()returned, unless the view assignsaudit_target. - A view can also assign
audit_actor(Nonerecords no account) andaudit_details, or overrideget_audit_fields().
Auditing from anywhere else
Outside of AuditViewMixin, from a view or deeper in a service, call audit.log. A category or types given here
wins over the action's:
audit.log(auditing.USER_PROVISION, target=user)
Without request=, the event reads the context of the request being served, audit.request_context(), which
AuditLogMiddleware captures when the request comes in. Outside a request, as in a Celery task, the context is empty
and the actor is the system.
Note
That context holds the Django request. DRF copies its user and auth onto it, but not its authenticator, so a call made inside a DRF view without
request=should passauth_method=.
How fields are filled
- Request fields are read from the request: the real client address, the path and the user agent.
http.request.idis the request id, settled byAuditLogMiddlewareright after dockerflow assigns it, and echoed in theDOCKERFLOW_REQUEST_ID_HEADER_NAMEresponse header (X-Request-IDby default). The inbound id is only kept whenREQUEST_ID_TRUST_HEADERis set. Otherwise the backend generates one, so a client never picks it. - The actor is derived from
request.user,request.authand the DRF authenticator, as described in Actors. The authenticator's class is mapped to an auth method byaudit.register_auth_method(see Configuration). Without a request, the actor is the system. You can override it withactor=,actor_type=,auth_method=andclient_id=. An explicitactor_type=keeps the account the request is signed in as.actor=Nonerecords no account: for a caller failing to authenticate, an application acting before it is given a delegated user, or the system. - The target is described by its model name, its primary key and the
fieldsits model is registered with, see Targets. A model that is not registered is still identified. The account the action is about,user.target, is found from the target, unless given asuser_target=. Name a service the backend calls withtarget_service=. Any other keyword argument lands underlasuite.details.
Emission never raises
A broken configuration or value is reported on the application logger (and to Sentry), and the business operation goes on. A registered field that cannot be read is left out of the target, and the event is still emitted.
Configuration
| Variable | Default | Meaning |
|---|---|---|
AUDIT_LOG_LEVEL |
INFO |
Level of the audit logger |
AUDIT_LOG_STREAM |
ext://sys.stdout |
Where the handler writes |
AUDIT_LOG_SERVICE_NAME |
meet |
service.name, and the dataset <name>.audit, with any - turned into _ |
AUDIT_LOG_DATA_STREAM_NAMESPACE |
default |
data_stream.namespace: events are routed to the data stream logs-meet.audit-<namespace>. It must not contain - |
NUM_PROXIES |
1 |
DRF's number of trusted proxies appending to X-Forwarded-For, shared with the throttles. The client is the entry that many positions from the right; anything a client injects lands further left and is ignored. 1 matches the ingress-nginx defaults; use 2 behind a load balancer that also appends |
REQUEST_ID_TRUST_HEADER |
False |
Reuse the inbound request id as http.request.id, so that the ingress, Gunicorn, application logs and audit events share one id. Only set it when the ingress overwrites the header (proxy_set_header X-Request-ID $request_id; on ingress-nginx, which otherwise forwards the client's): otherwise a client could pick the id of someone else's request |
DOCKERFLOW_REQUEST_ID_HEADER_NAME |
X-Request-ID |
The header carrying that id. Read on the request only when REQUEST_ID_TRUST_HEADER is set, always echoed on the response |
Registering models and authentication methods
The project describes itself to the audit facility in code, in core/auditing.py. Once the audit app is ready, it
imports the auditing module of every installed app.
audit.register(Model, ...) tells the audit log how to handle a model:
fields: the fields describing it as a target;admin_values: the fields whose before and after values may be recorded for admin writes;category: the ECS category of its admin writes;entity_type: its ECS entity type, when one of the allowed values fits (application,user...);user_target: the attribute holding the account an event on it is about, such asuserfor an access.
A proxy model falls back to its concrete model. Registering a model twice raises AlreadyRegistered, and an entity type
ECS does not allow raises ValueError.
audit.register_auth_method(klass, name) sets the lasuite.auth.method of a DRF authentication class or of a login
backend. A DRF class inherits the name of its closest registered base, and DRF's own classes are built in. A login
backend must be registered itself, because custom backends often subclass ModelBackend just for its permission
checks. ModelBackend is built in as password.