Django REST Framework ships with every endpoint open by default, and most of its serious vulnerabilities come from defaults that nobody changed rather than from bugs in the framework. If DEFAULT_PERMISSION_CLASSES is not set, DRF uses AllowAny; a ModelSerializer with fields = '__all__' exposes every column, including ones added next year; and object-level permissions silently do not apply to list views or creates. This guide covers how authentication, permissions, serializers, filtering and throttling work in DRF, where each one leads to broken object level authorisation (BOLA) or excessive data exposure, and a settings baseline you can apply to an existing API. It is for Python developers and application security engineers reviewing DRF code, and it builds on the framework-level Django Security Guide without repeating it.
Key takeaways
- Set a deny-by-default permission. Replace the implicit
AllowAnywithIsAuthenticated(or stricter) inDEFAULT_PERMISSION_CLASSES, and mark genuinely public views withAllowAnyexplicitly. - Scope the queryset, then check the object.
has_object_permissionruns only when a view callscheck_object_permissions(), which generic views do inget_object()but not for list responses or creates; filterget_queryset()by the requesting user. - List serializer fields explicitly. DRF's own documentation strongly recommends explicit
fields;'__all__'andexcludeexpose new model fields automatically and enable mass assignment. - Throttling is not a security control on its own. DRF says its throttles "should not be considered a security measure" against brute force or DoS, are non-atomic, and trust
X-Forwarded-Foronly as far asNUM_PROXIESallows. - Treat API tokens as machine identities. The built-in
TokenAuthenticationgives one simple token per user with no expiry described in the docs; use expiring, revocable tokens for anything beyond internal tooling.
How identity and access work in Django REST Framework
Every DRF request passes through three stages before the view body runs: authentication, permission checks and throttling. Authentication classes are tried in order; the first that succeeds sets request.user and request.auth, and if none succeed the user is AnonymousUser. Permissions then decide whether that user may call the view, and throttles decide whether they may call it now. The first authentication class on a view also decides whether a denied request gets a 401 with a WWW-Authenticate header or a 403.
| Authentication class | How it works | Security notes from the DRF docs | Use for |
|---|---|---|---|
SessionAuthentication | Django session cookie | Enforces CSRF tokens on authenticated unsafe methods (POST, PUT, PATCH, DELETE) | Browser front ends on the same site |
BasicAuthentication | HTTP Basic username and password on every request | "Generally only appropriate for testing"; HTTPS only in production | Tests; disable in production |
TokenAuthentication | Authorization: Token <key> against the Token model in rest_framework.authtoken | Described as "fairly simple"; the docs point to django-rest-knox for per-client tokens, expiry and logout | Internal tools, with care |
RemoteUserAuthentication | Trusts REMOTE_USER from the web server | Requires RemoteUserBackend; only safe if the proxy strips client-supplied values | SSO at the reverse proxy |
| Third party: django-rest-knox, djangorestframework-simplejwt | Per-client tokens; signed JWTs validated without a database lookup | Knox supports server-side logout; JWTs need short lifetimes and revocation design | Public and mobile APIs |
Permission classes
| Class | Grants access when | Object-level check |
|---|---|---|
AllowAny | Always (the default if nothing is configured) | No |
IsAuthenticated | User is authenticated | No |
IsAdminUser | user.is_staff is true | No |
IsAuthenticatedOrReadOnly | Authenticated, or anonymous for GET, HEAD, OPTIONS | No |
DjangoModelPermissions | User holds the model add, change or delete permission for the method | No |
DjangoObjectPermissions | User holds the permission on the specific object | Yes, needs an object permission backend such as django-guardian |
Permissions compose with &, | and ~ (precedence ~, then &, then |). A per-view permission_classes attribute replaces the global default rather than adding to it, so a view that sets permission_classes = [IsOwner] has dropped IsAuthenticated unless it is included again.
Object-level authorisation: closing BOLA in DRF
OWASP API Security Top 10 2023 lists API1 Broken Object Level Authorization first, and DRF makes it easy to introduce. The DRF permissions documentation sets out the three gaps:
- Object checks run only through
check_object_permissions(): generic views call it insideget_object(). A customget_object(), or an@actionthat fetches by primary key withModel.objects.get(pk=...), skips it. The OWASP DRF Cheat Sheet says to callself.check_object_permissions(self.request, obj)in any custom lookup. - List views do not apply object permissions per item: a
has_object_permissionthat returns false for another user's invoice still lets that invoice appear in the list endpoint. - Creates do not call
get_object(): a user can create a child object under a parent they do not own unless the serializer orperform_create()checks it.
The robust pattern is to scope data first and check objects second. Override get_queryset() to filter by self.request.user (or the user's tenant), so other users' objects return 404 from detail, update and delete as well as being absent from lists, because DRF filter backends and querysets apply to single-object lookups too. Then add a permission class with has_object_permission for rules that depend on the object's state, such as "only the author may edit while status is draft". In perform_create(), set ownership server-side with serializer.save(owner=self.request.user) and validate any parent foreign key against the user's scope. See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.
Serializers: excessive data exposure and mass assignment
A serializer is both the response schema and the input whitelist, so one setting controls two OWASP API Top 10 2023 risks: API3 Broken Object Property Level Authorization (which merged the 2019 Excessive Data Exposure and Mass Assignment entries) and, through writes, privilege changes.
| Serializer setting | Risk | Safer practice |
|---|---|---|
fields = '__all__' | Every model field returned and writable, including future fields | Explicit fields list per use case |
exclude = [...] | New model fields appear automatically | Avoid, as the OWASP cheat sheet advises |
depth = 1 or more | Related objects serialised in full | Declare nested serializers with explicit fields |
Fields such as is_staff, role, owner, tenant writable | Mass assignment and privilege escalation | read_only_fields, and set values in save() |
read_only_fields on a field declared explicitly on the serializer | Ignored; field stays writable | Set read_only=True on the declared field |
| Password or secret in output | Credential exposure | extra_kwargs = {'password': {'write_only': True}} (not for declared fields) |
Model properties or methods in fields | Computed internal data exposed | Review each name in fields |
- Separate read and write serializers: an admin view and a self-service view of the same model should use different serializer classes, chosen in
get_serializer_class()by role. - Validate on partial updates: on PATCH,
validate()receives only the fields supplied, so cross-field checks must read missing values fromself.instance;validate_<field>()is skipped for omitted optional fields. - Write nested writes yourself:
ModelSerializerdoes not support writable nested data by default; customcreate()andupdate()must apply the same ownership checks to nested objects.
The property-level risk is covered in the OWASP API Top 10 API3 Broken Object Property Level Authorization Guide.
Filtering, search and ordering
OrderingFilterdefaults: withoutordering_fields, clients can order by any readable serializer field, and'__all__'allows any model field. DRF warns this can leak data, for example by ordering on a password hash to infer values. Always setordering_fields.- Regex search: the
$prefix insearch_fieldsenables regex matching, which DRF warns can be abused for denial of service; avoid it for untrusted clients. - Filter fields:
filterset_fieldson related or sensitive fields can be used as an oracle (filtering by a hidden attribute and watching result counts). Expose only fields the client may see. - Pagination: the OWASP cheat sheet notes pagination is off unless configured; set
DEFAULT_PAGINATION_CLASSand a maximum page size to stop unbounded list responses.
Throttling and resource limits
DRF provides AnonRateThrottle (keys on IP), UserRateThrottle (keys on user ID, IP for anonymous) and ScopedRateThrottle (keys on a view's throttle_scope plus user or IP), with rates such as '100/day' or '5/min' in DEFAULT_THROTTLE_RATES. Three properties limit their value as a security control:
- Client identity: DRF uses
X-Forwarded-Forif present, elseREMOTE_ADDR. SetNUM_PROXIESto the exact number of trusted proxies; with the wrong value clients can spoof their address or every user behind one NAT shares a limit. - Atomicity: the built-in throttles use non-atomic cache operations, so concurrent requests can exceed the limit. Use a shared cache such as Redis across workers, since a per-process
LocMemCachemultiplies the limit by the number of processes. - Purpose: the DRF docs say throttling should not be considered a security measure against brute force or DoS. Put login and token endpoints behind gateway or proxy limits and account lockout logic too.
Use ScopedRateThrottle with tight scopes for login, password reset, token creation and expensive exports. More patterns are in the Rate Limiting and API Abuse Guide.
Integrations, API tokens and service accounts
DRF APIs are often called by other services, scripts and partners, each holding a credential that is a non-human identity. Common weaknesses are a DRF Token created once for a service user and never rotated, service users with is_staff so they pass IsAdminUser, and one token shared between several integrations so revocation breaks everything.
- One identity per integration: a dedicated user per calling service, with model or object permissions for only the endpoints it needs, never
is_staffor superuser. - Expiring, revocable tokens: django-rest-knox (per-client tokens, server-side logout) or short-lived JWTs from simplejwt, or OAuth 2.0 client credentials from your identity provider for service-to-service calls.
- Inventory and review: list every token-holding user, its owner and last use, and delete unused ones. See the JWT Security Guide for token design.
- Never log tokens: the OWASP cheat sheet says never to log passwords, API tokens or personal data.
Logging and detection
- Log security events with identity: failed authentication, permission denials (403 and 404 from scoped querysets), validation errors and throttle hits, with user ID, token identifier (not value), view name and object ID.
- BOLA signals: one user receiving many 404 or 403 responses on sequential object IDs, or a sudden increase in distinct object IDs accessed per user.
- Exposure signals: response size per endpoint increasing after a model migration often means a serializer with
'__all__'orexcludepicked up new fields. - Tests as detection: automated tests that call each detail and list endpoint as user A for user B's objects, and snapshot tests of serializer output keys.
| Metric | Target |
|---|---|
Views relying on implicit AllowAny | Zero |
Serializers using '__all__' or exclude | Zero |
| Endpoints covered by cross-user authorisation tests | 100% of object endpoints |
| API tokens without expiry or owner | Zero |
Views with OrderingFilter but no ordering_fields | Zero |
Hardening baseline
A starting REST_FRAMEWORK settings block for a production API:
DEFAULT_PERMISSION_CLASSES:['rest_framework.permissions.IsAuthenticated'].DEFAULT_AUTHENTICATION_CLASSES: only what you use, for example session authentication for the web front end plus your token scheme; removeBasicAuthentication.DEFAULT_THROTTLE_CLASSESandDEFAULT_THROTTLE_RATES: anon and user rates, plus scoped rates for login and token endpoints, backed by a shared cache.DEFAULT_PAGINATION_CLASSandPAGE_SIZE: set, with a maximum page size on custom paginators.DEFAULT_RENDERER_CLASSES: JSON only in production, removing the browsable API renderer unless it is needed.NUM_PROXIES: exact count of trusted proxies.- Django settings:
DEBUG = FalseandDEBUG_PROPAGATE_EXCEPTIONS = False, randomSECRET_KEYfrom a secrets manager, as covered in the Django Security Guide. - Injection: no user input in
raw(),extra()orcursor.execute()without parameters;yaml.SafeLoaderonly; never unpickle client data.
Rolling it out on an existing API
- Inventory: list every view, its effective permission, authentication and throttle classes, and its serializer field mode, using a management command that walks the URL resolver.
- Add tests first: write cross-user and anonymous access tests for each object endpoint, so tightening defaults shows exactly what breaks.
- Flip the default permission in a staging environment, add explicit
AllowAnyto the views that must stay public, and fix the failures. - Convert serializers from
'__all__'andexcludeto explicit fields, comparing response snapshots before and after to avoid breaking clients. - Add queryset scoping and throttles, release behind a feature flag where possible, and roll back by reverting the settings change if a client breaks unexpectedly.
Common misconfigurations
- No global permission set: every new view is public until someone remembers to add a class.
- Per-view
permission_classesthat drop authentication: overriding the attribute replaces the default, it does not extend it. - Custom actions that bypass
get_object():@action(detail=True)methods that fetch by ID directly skip object permissions. - Object permission without queryset scoping: list endpoints still return other users' records.
- Writable role and ownership fields:
'__all__'serializers let a client setis_staff,owneror tenant identifiers. - Throttling as the only brute-force control: spoofable client IPs and non-atomic counters leave login endpoints exposed.
Framework mapping
| Framework | Reference | DRF control |
|---|---|---|
| OWASP API Security Top 10 2023 | API1 Broken Object Level Authorization | Queryset scoping, check_object_permissions() |
| OWASP API Security Top 10 2023 | API3 Broken Object Property Level Authorization | Explicit serializer fields, read-only fields |
| OWASP API Security Top 10 2023 | API4 Unrestricted Resource Consumption | Pagination, throttles, gateway limits |
| OWASP API Security Top 10 2023 | API5 Broken Function Level Authorization | DEFAULT_PERMISSION_CLASSES, role-based permission classes |
| NIST SP 800-53 Rev. 5 | AC-3, AC-6, AC-7, SC-5, SI-10, AU-12 | Access enforcement, least privilege, logon limits, DoS protection, input validation, logging |
Practitioner checklist
- Set
DEFAULT_PERMISSION_CLASSEStoIsAuthenticatedand mark public views explicitly. - Filter
get_queryset()by user or tenant on every view that returns owned data. - Call
check_object_permissions()in every custom lookup and custom action. - Validate ownership of parent objects in
perform_create()and set owners server-side. - Replace
'__all__'andexcludewith explicit fields; mark role and ownership fields read-only. - Set
ordering_fields, avoid regex search for untrusted clients, and enable pagination. - Configure scoped throttles on authentication endpoints, a shared cache and the correct
NUM_PROXIES. - Replace non-expiring DRF tokens for integrations with expiring, revocable tokens and one identity per integration.
- Add cross-user authorisation tests and serializer output snapshots to CI.
Standards and references
- Django REST Framework: Permissions
- Django REST Framework: Serializers
- Django REST Framework: Authentication
- Django REST Framework: Throttling
- Django REST Framework: Filtering
- OWASP: Django REST Framework Cheat Sheet
- OWASP: API Security Top 10 2023
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Django Security Guide · API Security Guide · FastAPI Security Guide · OWASP API Top 10 API1 Broken Object Level Authorization Guide