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 uid
and 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))
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']})
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.
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.
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 theuid
andtoken
, 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.
Top comments (1)
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