Security & Authorization
Understand why Django is secure by default — CSRF, XSS, SQL injection — and control who is allowed to do what with permissions.
What you will learn
- Explain the protections Django gives you for free (CSRF, XSS, SQL injection)
- Tell authentication apart from authorization
- Restrict actions with permissions and tests
Django is secure by default
Django was built by a news team that needed safe code fast, so it blocks the most common web attacks out of the box — as long as you do not switch the protections off. Knowing what they are (and why) is expected of any backend developer.
| Attack | What it is | Django’s built-in defence |
|---|---|---|
| CSRF | a hostile site tricks the browser into submitting your form | the {% csrf_token %} tag + a checked token |
| XSS | attacker injects <script> into a page | templates auto-escape output by default |
| SQL injection | attacker sneaks SQL through an input | the ORM parameterises every query |
CSRF — beyond just typing the tag
You met {% csrf_token %} in the forms lesson. Here is why it works: Django plants a secret, per-session token in every form and also in a cookie. When the form comes back, Django checks the two match. A malicious other site can make your browser submit a form, but it cannot read your token, so its forged request is rejected. That is the whole defence — never disable it.
XSS — auto-escaping
XSS (Cross-Site Scripting) is when an attacker stores something like <script>steal()</script> in, say, a comment, hoping it runs in other users’ browsers. Django templates auto-escape every variable — turning < into < — so the script is shown as harmless text, not executed:
<!-- if comment = "<script>alert('x')</script>" -->
<p>{{ comment }}</p>
<!-- Django renders it ESCAPED, so the browser shows the text,
it does NOT run the script:
<p><script>alert('x')</script></p> -->Watch out: Only ever use the |safe filter or {% autoescape off %} on content you 100% trust. Marking user input "safe" re-opens the XSS hole you were protected from.
Authentication vs authorization
These two words sound alike but mean different things — and interviewers love to ask the difference:
- Authentication = who are you? Logging in proves identity (the auth lesson’s
loginand@login_required). - Authorization = what are you allowed to do? A logged-in user might still be forbidden from deleting someone else’s post.
Logging a user in is not enough — you must also check they are allowed to perform an action. Django has a built-in permissions system. Every model automatically gets add/change/delete/view permissions, and you check them in views:
from django.contrib.auth.decorators import login_required, permission_required
@login_required # must be logged in (authn)
@permission_required("blog.delete_post", raise_exception=True) # must be allowed (authz)
def delete_post(request, pk):
# only users WITH the delete permission reach here
...How the two decorators combine, step by step:
@login_requiredruns first: not logged in → sent to the login page.@permission_required("blog.delete_post", ...)runs next: logged in but lacking that permission → blocked with a 403 "Forbidden".- Only a user who is both logged in and holds the permission runs the view body.
- You grant permissions per-user or per-group in the admin, or check ownership in code (e.g. "is this the post’s author?").
Note: Output: a random logged-in visitor who tries to delete another person’s post hits a 403 Forbidden — they were authenticated (logged in) but not authorized (lacked permission). That is exactly the gap a login-only check would miss.
A few settings that matter in production
Security also lives in settings.py. The big ones (covered fully in the deployment lesson):
DEBUG = Falsein production — aTruedebug page leaks your code and settings to attackers.SECRET_KEYkept out of the code (in an environment variable) — it signs sessions and tokens.ALLOWED_HOSTSset to your real domain so the site only answers for addresses you own.
Tip: Reuse Django’s protections — do not roll your own. Keep {% csrf_token %} on forms, never mark untrusted input |safe, always query through the ORM, and pair @login_required (authentication) with a permission or ownership check (authorization).
Q. A logged-in user tries to delete a post that belongs to someone else and gets “403 Forbidden”. Which protection stopped them?
✍️ Practice
- Explain in your own words how the CSRF token blocks a forged form submission.
- Add
@permission_required(or an ownership check) to your delete view so users can only delete their own records.
🏠 Homework
- Write down, for one of your views, who is authenticated vs authorized to use it, and add a test that a forbidden user gets a 403.