Hey everyone! Last month we rotated our secret keys in our production environment, to keep up with the best security practices. There was some friction and I will tell you how to avoid it. There are millions of users in this particular app so tiny bit of convenience multiplied by a million can be a problem.
This solution will work if you are using database backed sessions, which I assume majority of websites do and that’s the default configuration (https://docs.djangoproject.com/en/3.0/ref/settings/#session-engine), otherwise still read on it will help you to roll out your version that works for your session engine.
Press enter or click to view image in full size
What is Secret Key?
A secret key for a particular Django installation. This is used to provide cryptographic signing, and should be set to a unique, unpredictable value.
The most important usecase for secret key, used by almost all projects, is signing user session data. So we will be focusing on sessions. This is extremely important if you are using Cookie based sessions (“django.contrib.sessions.backends.signed_cookies”).
Possible reasons to change Secret Key
First scenario, your secret key is compromised, if that’s the case stop reading and change the secret key restart the server and start praying the damage is not huge. This is not the case you’d care about (not) logging out users.
Second scenario, as best practice you want to rotate your keys every year, but you want to do this without causing inconvenience to your users by logging them out.
Naive Approach
You change secret key (wherever you are keeping it!), restart the server, that’s it. The downside is, you will experience some minor problems now. Don’t worry password authentication still works, but couple things will break:
- Users will be logged out and you will see a lot of Session data corrupted errors.
- Already sent out password reset emails won’t work.
- If you are using custom low-level crypto API, it will expectedly break.
Press enter or click to view image in full size
Pro Approach (using django-rotate-secret-key library)
About the downsides of naive approach, 2nd is not a big deal users can request another one, 3rd is too specific no generic solution can prevent that. BUT we can prevent the first problem. 💡
The basic idea is whenever Django tries to verify the session hash and fails because the session was created with OLD_KEY and now we are using NEW_KEY, we will do a second try with the OLD_KEY, and rewrite the session with the NEW_KEY (after verifying that it’s authentic with OLD_KEY). Of course you have to decide how long you want to keep doing this, if you keep doing it forever there is no reason to change keys at all. Depends on your website but 1 month is a good timeframe to allow both keys. This means users coming once a month (or more) will not be affected by this change (probably 99% of the cases) and only a very small portion will be logged out. Much better than 100%.
Actual Implementation / Solution
Enough “into” I will summarize what you need to do in couple steps, tomorrow you want to rotate your secret key, what you do?
- pip3 install django-rotate-secret-key
- Add ‘rotatesecretkey’ to INSTALLED APPS
- In MIDDLEWARE replace ‘django.contrib.auth.middleware.AuthenticationMiddleware’ with ‘rotatesecretkey.middleware.RotateAuthenticationMiddleware’
- Set SESSION_ENGINE = ‘rotatesecretkey.sessions’
- Add OLD_SECRET_KEY = {YOUR CURRENT KEY}
- SECRET_KEY = {YOUR NEWLY GENERATED KEY}
- (Conditional) If you get “admin.E408” error when booting up because some django versions don’t recognize RotateAuthenticationMiddleware is indeed an AuthenticationMiddleware, add SILENCED_SYSTEM_CHECKS = [‘admin.E408’,]
now when this code hits production, new users will start using SECRET_KEY no problem, old users’ sessions will be decoded by OLD_SECRET_KEY and rehashed with SECRET_KEY so one visit is enough to “upgrade” them to the newer key.
After 1 (or N) month:
Revert the above steps, now you can uninstall the library, remove everything about “rotatesecretkey” since supporting an old key too long is against the idea of key rotating in the first place. Just keep SECRET_KEY the same/new value. Everyone should be using that anyway.
Press enter or click to view image in full size
Congratulations! Normally security and UX is a trade-off, but with the help of rotatesecretkey you (A) Applying the best practices with rotating your key (B) making it transparent to users :)
Reach me out at @eralpbayraktar in case you have any problems/questions!
Disclaimer: I’m the author of django-rotate-secret-key https://github.com/EralpB/django-rotate-secret-key/tree/master/rotatesecretkey