Going DeeperExtra· 40 min read

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.

AttackWhat it isDjango’s built-in defence
CSRFa hostile site tricks the browser into submitting your formthe {% csrf_token %} tag + a checked token
XSSattacker injects <script> into a pagetemplates auto-escape output by default
SQL injectionattacker sneaks SQL through an inputthe 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 &lt; — so the script is shown as harmless text, not executed:

Templates auto-escape — XSS is neutralised
<!-- 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>&lt;script&gt;alert('x')&lt;/script&gt;</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 login and @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:

loginrequired = who · permissionrequired = what
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:

  1. @login_required runs first: not logged in → sent to the login page.
  2. @permission_required("blog.delete_post", ...) runs next: logged in but lacking that permission → blocked with a 403 "Forbidden".
  3. Only a user who is both logged in and holds the permission runs the view body.
  4. 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 = False in production — a True debug page leaks your code and settings to attackers.
  • SECRET_KEY kept out of the code (in an environment variable) — it signs sessions and tokens.
  • ALLOWED_HOSTS set 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?

Answer: They passed authentication (they were logged in) but failed authorization — they did not have permission to delete that post. Login alone never decides what a user is allowed to do.

✍️ Practice

  1. Explain in your own words how the CSRF token blocks a forged form submission.
  2. Add @permission_required (or an ownership check) to your delete view so users can only delete their own records.

🏠 Homework

  1. 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.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →