<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>AcePython</title><link href="https://acepython.com/" rel="alternate"/><link href="https://acepython.com/feeds/all.atom.xml" rel="self"/><id>https://acepython.com/</id><updated>2026-04-14T00:00:00-07:00</updated><entry><title>The first security mistake people make in Django</title><link href="https://acepython.com/blog/first-django-security-mistake/" rel="alternate"/><published>2026-04-14T00:00:00-07:00</published><updated>2026-04-14T00:00:00-07:00</updated><author><name>Philip James</name></author><id>tag:acepython.com,2026-04-14:/blog/first-django-security-mistake/</id><summary type="html">&lt;p&gt;Oops I committed my secret key!&lt;/p&gt;</summary><content type="html">&lt;h2&gt;You've just started a Django project...&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;
&lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="n"&gt;startproject&lt;/span&gt; &lt;span class="n"&gt;mysite&lt;/span&gt; &lt;span class="n"&gt;djangotutorial&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;First commit&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Oops. Django created a secret key for you, and you just committed it to your git repo. Your app is now insecure, but how? And what do you do about it?&lt;/p&gt;
&lt;p&gt;If you look inside your &lt;code&gt;settings.py&lt;/code&gt;, which in the case above would be &lt;code&gt;djangotutorial/mysite/settings.py&lt;/code&gt;, you'll see a &lt;code&gt;SECRET_KEY&lt;/code&gt; variable right near the top, along with a strong warning to keep the production secret key, well, secret. In modern Django versions, the key is prefixed with &lt;code&gt;django-insecure&lt;/code&gt;, just to make it super-duper clear this shouldn't be the real key you use.&lt;/p&gt;
&lt;p&gt;Django tries so hard to keep you from accidentally leaking your secret key because it's the foundation of authentication inside the framework. There's two critical places the secret key is used: Inside the &lt;code&gt;PasswordResetTokenGenerator&lt;/code&gt; and in &lt;code&gt;get_cookie_signer&lt;/code&gt;. Let's handle these one at a time.&lt;/p&gt;
&lt;p&gt;Django's built-in authorization system includes utilities for handling password resets. When a reset is requested for a user, the &lt;code&gt;PasswordResetTokenGenerator&lt;/code&gt; generates a salted and hashed token that can be sent to the user. The secret key is used as the primary component of the salt. When the user loads the password reset view with the token, a token using the same salt and key is generated, and the user-provided token is compared to this new token. The password reset token is not stored in the database; it's generated on the fly and compared for authenticity.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pseudocode of what&amp;#39;s happening&lt;/span&gt;

&lt;span class="n"&gt;possible_match_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PasswordResetTokenGenerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_supplied_token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;possible_match_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;let_user_change_password&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reject_password_change_attempt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If your secret key is compromised, then an attacker can generate a correct password reset token to reset any user's password.&lt;/p&gt;
&lt;p&gt;Sadly that's not the worst part. &lt;code&gt;get_cookie_signer&lt;/code&gt; uses the secret key to sign session cookies. Django's session cookies are what the browser stores and sends to tell the server who the current user is. If an attacker gets access to your secret key, they can forge session cookies and impersonate any user, including admin users.&lt;/p&gt;
&lt;p&gt;So, if leaking the secret key is so bad, and unfortunately so easy to casually commit, what do you do when it happens? The best way is to use the same function Django does:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;djangotutorial&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_random_secret_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;get_random_secret_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;e_yc)hw!+t7t^^l1k=1tpamz1)*hhopn^!ne-bqegqa7*nwmfe&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you have your new secret key, the best practice would be to have your settings file read that from an environment variable or other secret store, but that's something we'll cover in a future post.&lt;/p&gt;</content><category term="posts"/><category term="django"/><category term="security"/></entry></feed>