DEV Community

Cover image for Building a Secure User Activation System in Django
Henry Ndou
Henry Ndou

Posted on

4 1 1 1 1

Building a Secure User Activation System in Django

When I set out to build an authentication pipeline for an app I am working on, I had a clear vision: abstract away the complexity of user activation while keeping it secure and flexible. I wanted users to register, receive a unique, short-lived activation link, and only become active after clicking it—no shortcuts, no compromises. The twist? I would generate a random 32-character activation key, pair it with Djoser’s built-in uidand token, and store it in Redis with a 15-minute expiration. If that key expired, an endpoint would generate a fresh one, still tied to the original parameters. It sounded simple—until I dove in.

The Initial Idea: Abstraction

My goal was to leverage Djoser, a powerful Django REST Framework library, which generates a uid (a base64-encoded user ID) and token for activation. Instead of exposing these directly in an email link like /activate/{uid}/{token}, I would create a custom key—say, x7k9p2m4q8r5t1v3n6j0h2b4y5l8z9w3—and map it to the uid and token behind the scenes. The email would then point to /api/activate-account/{key}, keeping the sensitive bits hidden. Redis, an in-memory data store, would hold this mapping temporarily, expiring it after 15 minutes to enforce security. A second endpoint would refresh the key if needed, ensuring users could still activate without re-registering. It was ambitious, a bit overkill for a learning project, but I was hooked on the challenge.

The Implementation

Getting this to work was no picnic. I started by customizing Djoser’s email system. In email.py, I tapped into the ActivationEmail class to grab the uid and token Djoser generates:

activation_key = str(uuid.uuid4())[:32]  # A 32-char snippet of a UUID
data = {'uid': uid, 'token': token}
redis_client.setex(activation_key, 900, json.dumps(data))
Enter fullscreen mode Exit fullscreen mode

This stored the key in Redis, expiring after 900 seconds (15 minutes). The email link became /api/activate-account/{activation_key}, clean and abstract. Next, I built a view to handle it:

data = json.loads(redis_client.get(activation_key))
requests.post('http://localhost:8000/api/auth/users/activation/', json={'uid': data['uid'], 'token': data['token']})
Enter fullscreen mode Exit fullscreen mode

The view fetched the uid and token from Redis and called Djoser’s activation endpoint internally. Success came after wrestling with Redis connection errors and serializer issues—more on that later.

Image description

Why Djoser? The Power of Customizability

Django offers several authentication options, each with its strengths:

  • Django’s Built-in Auth: Simple, great for basic apps. It provides login, logout, and password management out of the box, but it’s not REST-friendly and lacks API-first design.

  • Django Allauth: A heavyweight for social logins and complex flows. It’s robust but overkill if you’re focused on REST APIs and custom pipelines.

  • Django REST Framework (DRF) Simple JWT: Lightweight, token-based auth for APIs. Perfect for stateless systems, but it doesn’t handle email activation natively.

  • Djoser: The sweet spot for RESTful authentication. It integrates with DRF, offering endpoints for registration, login, and activation, all customizable via serializers and email overrides.

I chose Djoser for its balance of power and flexibility. It abstracts away the grunt work—user creation, token generation, email sending—while letting me tweak the pipeline. With a custom serializer, I could enforce unique usernames, and with an email override, I could swap out the default link for my activation key. It is like having a sturdy scaffold I could paint my own colours on.

Image description

Why Redis? Learning and Separation of Concerns

Redis was not strictly necessary—PostgreSQL could have stored my activation keys—but I picked it for two reasons:

  • Learning Curve: I had never used Redis before, and its reputation as a fast, in-memory store intrigued me. Why not dive in and see what it could do?

  • Separation of Concerns: Storing transient activation data in Redis keeps it separate from my persistent PostgreSQL database. This reduces clutter in my main storage and leverages Redis’s built-in expiration, avoiding manual cleanup.

The trade-off? Setup complexity. On Windows, Redis is not native, so I downloaded a locally runnable instance of Redis from GitHub, ran redis-server.exe, and hit a wall with “Connection refused” errors. Turns out, Redis was not running—or port 6379 was blocked. A quick netstat -ano | findstr :6379 and a firewall tweak later, it all hummed along.

The Pipeline’s Security Edge

This auth pipeline stands out for its security:

  • Obscured Parameters: Unlike Djoser’s default /activate/{uid}/{token}, my /api/activate-account/{key} hides the uid and token, reducing exposure in emails.

  • Short-Lived Keys: Redis’s 15-minute expiration ensures keys cannot be reused indefinitely, thwarting replay attacks.

  • Regeneration Capability: The refresh endpoint adds resilience—if a key expires, users get a new one without re-registering.

  • Layered Validation: The view checks Redis before calling Djoser’s endpoint, adding an extra gate against invalid requests.

Compared to simpler options like Django’s built-in auth (no API focus) or JWT (no native email flow), this setup balances security and usability while teaching me Redis’s quirks.

Reflections: Beauty in Working Code

“Code is beauty when it works,” and I felt that when the pieces clicked. From serializer woes to Redis hiccups, the journey taught me Djoser’s power lies in its hooks—custom serializers and emails—and Redis shines for ephemeral data. It is overkill for a small app, sure, but the lessons in abstraction, security, and system design? Priceless.

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (1)

Collapse
 
vndlovu profile image
Vuyisile Ndlovu

Nice article, I like that you used Redis because you hadn't used it before and were drawn to adding more complexity to your project :D

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay