Join our Newsletter — 33% off our NHI Course
Home› Guides› Django REST Framework Security Guide: Permissions, Throttling and…
Guide Application Security

Django REST Framework Security Guide: Permissions, Throttling and Serializer Exposure

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

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 AllowAny with IsAuthenticated (or stricter) in DEFAULT_PERMISSION_CLASSES, and mark genuinely public views with AllowAny explicitly.
  • Scope the queryset, then check the object. has_object_permission runs only when a view calls check_object_permissions(), which generic views do in get_object() but not for list responses or creates; filter get_queryset() by the requesting user.
  • List serializer fields explicitly. DRF's own documentation strongly recommends explicit fields; '__all__' and exclude expose 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-For only as far as NUM_PROXIES allows.
  • Treat API tokens as machine identities. The built-in TokenAuthentication gives 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 classHow it worksSecurity notes from the DRF docsUse for
SessionAuthenticationDjango session cookieEnforces CSRF tokens on authenticated unsafe methods (POST, PUT, PATCH, DELETE)Browser front ends on the same site
BasicAuthenticationHTTP Basic username and password on every request"Generally only appropriate for testing"; HTTPS only in productionTests; disable in production
TokenAuthenticationAuthorization: Token <key> against the Token model in rest_framework.authtokenDescribed as "fairly simple"; the docs point to django-rest-knox for per-client tokens, expiry and logoutInternal tools, with care
RemoteUserAuthenticationTrusts REMOTE_USER from the web serverRequires RemoteUserBackend; only safe if the proxy strips client-supplied valuesSSO at the reverse proxy
Third party: django-rest-knox, djangorestframework-simplejwtPer-client tokens; signed JWTs validated without a database lookupKnox supports server-side logout; JWTs need short lifetimes and revocation designPublic and mobile APIs

Permission classes

ClassGrants access whenObject-level check
AllowAnyAlways (the default if nothing is configured)No
IsAuthenticatedUser is authenticatedNo
IsAdminUseruser.is_staff is trueNo
IsAuthenticatedOrReadOnlyAuthenticated, or anonymous for GET, HEAD, OPTIONSNo
DjangoModelPermissionsUser holds the model add, change or delete permission for the methodNo
DjangoObjectPermissionsUser holds the permission on the specific objectYes, 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 inside get_object(). A custom get_object(), or an @action that fetches by primary key with Model.objects.get(pk=...), skips it. The OWASP DRF Cheat Sheet says to call self.check_object_permissions(self.request, obj) in any custom lookup.
  • List views do not apply object permissions per item: a has_object_permission that 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 or perform_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 settingRiskSafer practice
fields = '__all__'Every model field returned and writable, including future fieldsExplicit fields list per use case
exclude = [...]New model fields appear automaticallyAvoid, as the OWASP cheat sheet advises
depth = 1 or moreRelated objects serialised in fullDeclare nested serializers with explicit fields
Fields such as is_staff, role, owner, tenant writableMass assignment and privilege escalationread_only_fields, and set values in save()
read_only_fields on a field declared explicitly on the serializerIgnored; field stays writableSet read_only=True on the declared field
Password or secret in outputCredential exposureextra_kwargs = {'password': {'write_only': True}} (not for declared fields)
Model properties or methods in fieldsComputed internal data exposedReview 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 from self.instance; validate_<field>() is skipped for omitted optional fields.
  • Write nested writes yourself: ModelSerializer does not support writable nested data by default; custom create() and update() 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

  • OrderingFilter defaults: without ordering_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 set ordering_fields.
  • Regex search: the $ prefix in search_fields enables regex matching, which DRF warns can be abused for denial of service; avoid it for untrusted clients.
  • Filter fields: filterset_fields on 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_CLASS and 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-For if present, else REMOTE_ADDR. Set NUM_PROXIES to 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 LocMemCache multiplies 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_staff or 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__' or exclude picked 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.
MetricTarget
Views relying on implicit AllowAnyZero
Serializers using '__all__' or excludeZero
Endpoints covered by cross-user authorisation tests100% of object endpoints
API tokens without expiry or ownerZero
Views with OrderingFilter but no ordering_fieldsZero

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; remove BasicAuthentication.
  • DEFAULT_THROTTLE_CLASSES and DEFAULT_THROTTLE_RATES: anon and user rates, plus scoped rates for login and token endpoints, backed by a shared cache.
  • DEFAULT_PAGINATION_CLASS and PAGE_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 = False and DEBUG_PROPAGATE_EXCEPTIONS = False, random SECRET_KEY from a secrets manager, as covered in the Django Security Guide.
  • Injection: no user input in raw(), extra() or cursor.execute() without parameters; yaml.SafeLoader only; never unpickle client data.

Rolling it out on an existing API

  1. 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.
  2. Add tests first: write cross-user and anonymous access tests for each object endpoint, so tightening defaults shows exactly what breaks.
  3. Flip the default permission in a staging environment, add explicit AllowAny to the views that must stay public, and fix the failures.
  4. Convert serializers from '__all__' and exclude to explicit fields, comparing response snapshots before and after to avoid breaking clients.
  5. 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_classes that 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 set is_staff, owner or tenant identifiers.
  • Throttling as the only brute-force control: spoofable client IPs and non-atomic counters leave login endpoints exposed.

Framework mapping

FrameworkReferenceDRF control
OWASP API Security Top 10 2023API1 Broken Object Level AuthorizationQueryset scoping, check_object_permissions()
OWASP API Security Top 10 2023API3 Broken Object Property Level AuthorizationExplicit serializer fields, read-only fields
OWASP API Security Top 10 2023API4 Unrestricted Resource ConsumptionPagination, throttles, gateway limits
OWASP API Security Top 10 2023API5 Broken Function Level AuthorizationDEFAULT_PERMISSION_CLASSES, role-based permission classes
NIST SP 800-53 Rev. 5AC-3, AC-6, AC-7, SC-5, SI-10, AU-12Access enforcement, least privilege, logon limits, DoS protection, input validation, logging

Practitioner checklist

  • Set DEFAULT_PERMISSION_CLASSES to IsAuthenticated and 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__' and exclude with 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

Related NHI Mgmt Group resources: Django Security Guide · API Security Guide · FastAPI Security Guide · OWASP API Top 10 API1 Broken Object Level Authorization Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.