11603
Programming

Secure Note-Taking API: Django, DRF & JWT Scoping Explained

Posted by u/Lolpro Lab · 2026-05-06 06:03:26

Building an API that lets each user see only their own data is a common challenge in modern web development. Traditional session-based authentication falls apart when your frontend and backend live on different domains—like a React app on Netlify talking to a Django API on PythonAnywhere. JSON Web Tokens (JWT) solve this by providing a stateless, cross-domain authentication method. But authentication alone isn't enough; you also need scoping—ensuring User A never accesses User B's notes, even if they guess an ID. This guide breaks down how to build a scoped note-taking API using Django Rest Framework and SimpleJWT, covering every step from custom user models to token expiry handling.

What Is the Core Problem with Session Authentication for Modern APIs?

Session-based authentication relies on the server storing user session data and sending a cookie to the client. This works when your frontend and backend are on the same server because the browser automatically includes the cookie. However, when they're separated—say, a React app on Netlify and a Django API on PythonAnywhere—cookies don't travel across domains. The browser blocks cross-origin cookie sharing unless CORS is explicitly configured, which is complex and often insecure. Additionally, sessions are stateful: the server must maintain a database or cache of active sessions, adding overhead and limiting scalability. JWTs eliminate these issues by encoding user identity directly into the token. The server simply verifies the token's signature without storing anything, making it ideal for distributed frontend-backend setups.

Secure Note-Taking API: Django, DRF & JWT Scoping Explained
Source: www.freecodecamp.org

How Does JWT Authentication Work in Django Rest Framework?

JWT authentication in DRF uses a token that contains three parts: a header (algorithm type), a payload (user ID, expiration, etc.), and a signature. When a user logs in, the server validates their credentials and issues an access token (short-lived, typically 5-15 minutes) and a refresh token (longer-lived, sometimes days). The client sends the access token in the Authorization: Bearer <token> header for every API request. The server extracts and verifies the token's signature using a secret key. If valid, it trusts the user's identity. When the access token expires, the client uses the refresh token to get a new one without requiring re-login. SimpleJWT is a popular DRF library that handles token generation, validation, and rotation. You configure it by adding REST_FRAMEWORK settings and URL endpoints for token obtain and refresh.

What Is “Scoping” and Why Is It Critical for a Note-Taking API?

Scoping means restricting data access so that each user can only see, edit, or delete their own records. In a note-taking API, User A should never see User B's notes, even if they guess the note's ID. Without scoping, a malicious user could enumerate IDs and steal or tamper with others' data. This is especially important in public APIs where user IDs and note IDs are sequential. Scoping is implemented by filtering queries based on the authenticated user. For example, in a Django view, you retrieve notes using Note.objects.filter(user=request.user). The view never queries all notes; it always filters by the current user. Additionally, you should avoid exposing note IDs in URLs that allow unauthorized access. Instead, use hyperlinked relationships or ensure that update/delete views check ownership before proceeding. Scoping also prevents ID enumeration attacks because the server returns 404 or 403 for IDs not owned by the user.

How Do You Set Up a Custom User Model in Django for JWT Authentication?

A custom user model is essential for JWT because it gives you control over authentication fields. In your Django app, create a model that inherits from AbstractBaseUser or AbstractUser. For a note-taking API, you might start with email-based login by setting USERNAME_FIELD = 'email' and REQUIRED_FIELDS = []. Define the model with fields like email, password, and optional name. Next, create a UserManager that extends BaseUserManager to handle user creation. After defining the model, update settings.py with AUTH_USER_MODEL = 'notes.User' (where 'notes' is your app name). Then run makemigrations and migrate before adding any other models. Using a custom model early prevents migration headaches later and allows easy extension, e.g., adding profile fields or requiring email verification. SimpleJWT works seamlessly with any custom user model as long as it has the required authentication fields.

Secure Note-Taking API: Django, DRF & JWT Scoping Explained
Source: www.freecodecamp.org

How Do You Create Scoped Views for Notes Using Django Rest Framework?

Scoped views in DRF are most easily implemented with ModelViewSet and overriding get_queryset. Define a NoteViewSet that uses NoteSerializer and set permission_classes = [IsAuthenticated]. In get_queryset, return Note.objects.filter(user=self.request.user). This ensures all list and detail queries are scoped. For create operations, override perform_create to save the note with the current user: serializer.save(user=self.request.user). This prevents users from manually setting the user field via the request data. The view automatically handles scoping for detail, update, and delete actions because the queryset is filtered. Additionally, consider using a mixin or custom permission class to enforce ownership on any view. This approach also prevents ID enumeration: if a user requests a note ID they don't own, the queryset returns empty, and DRF raises a 404 instead of 403, making it unclear whether the ID exists.

How Do You Test the API and Handle Token Expiration in Practice?

Testing the API with a tool like Postman involves several steps. First, register a user by sending a POST request to /api/register/ with email and password. The endpoint returns user data but no token—you must then obtain tokens via a login endpoint (e.g., /api/token/) that returns access and refresh tokens. Use the access token in the Authorization header for subsequent requests. Create a note with a POST to /api/notes/, then list notes with a GET to the same URL. To demonstrate scoping, create a second user and attempt to access the first user's note ID; you should receive a 404. For token expiration, SimpleJWT allows configuring ACCESS_TOKEN_LIFETIME and REFRESH_TOKEN_LIFETIME in settings. When the access token expires, the server returns a 401. The client must then call the token refresh endpoint with the refresh token to get a new access token. Implement refresh token rotation to enhance security: invalidate old refresh tokens each time a new one is issued. This pattern keeps your API stateless while maintaining security.

What Are the Key Benefits of Using SimpleJWT Over Other Authentication Libraries?

SimpleJWT is specifically built for Django Rest Framework and follows the JWT standard with minimal configuration. It integrates tightly with DRF's permission and authentication classes, so you don't need to write custom middleware. Features like token refresh, blacklisting (via RefreshToken.for_user), and sliding sessions are built-in. Unlike libraries like django-rest-framework-jwt (deprecated), SimpleJWT is actively maintained and supports modern best practices like token rotation and custom token payloads. It also works seamlessly with custom user models and provides an admin interface for managing tokens if needed. For a scoped note-taking API, SimpleJWT's simplicity means you can focus on business logic rather than authentication boilerplate. Its documentation is clear, and the community is strong. Overall, it's the go-to choice for JWT authentication in DRF projects as of 2025.