MAD Study Hub

Home / MAD1 / Week 9

Week 9 · Security: authentication, sessions, tokens and attacks

Official syllabus line: Application Security
About 100 minEnd Term material (see exam radar)BSCS2003 (+ project BSCS2003P) · Thejesh G N, Nitin Chandrachoodan
Before you start: MAD1 weeks 1-8 (Flask routes, forms, Jinja, SQLAlchemy models, REST/JSON, JavaScript and fetch); week 7 for the idea of statelessness and caching.

The one-minute story

Think of it like this

A building with guards. A visitor arrives. At the gate the guard asks "who are you?" and checks an ID card (authentication). Inside, a second question is asked at every door: "are you allowed in this room?" (authorisation). You do not keep a copy of the visitor's secret PIN: you keep only a scrambled fingerprint of it, so a thief who steals your register cannot read PINs (password hashing). After the check, the guard gives a numbered wristband (session cookie) so he need not re-check at every door. Meanwhile attackers try tricks: slipping a forged note to a clerk (CSRF), leaving a booby-trapped sticker on the notice board that harms whoever reads it (XSS), or whispering clever words into a form so the database obeys them (SQL injection). Security is mostly a list of habits, each one closing one of these doors.

By the end of this week you can:

  • explain authentication vs authorisation, and RBAC vs ABAC;
  • store and check passwords safely with generate_password_hash and check_password_hash;
  • explain and build session-based login with cookies, Flask session and Flask-Login (login_user, @login_required, roles);
  • name and prevent CSRF, XSS and SQL injection;
  • explain HTTPS/TLS, secrets in environment variables, input validation, rate limiting, token authentication and least privilege;
  • predict the output of Flask login/session snippets and use a security checklist for your project.

Words you need

Words you need
  • Authentication (authn) - proving who you are (username and password, token, fingerprint).
  • Authorisation (authz) - deciding what you may do once known (admin or normal user).
  • Hash - a one-way scramble of data into a fixed-size fingerprint; it cannot be reversed.
  • Salt - random extra data mixed in before hashing so equal passwords get different hashes.
  • Session - the server's memory of a logged-in user between requests.
  • Cookie - a small named value the server asks the browser to store and send back with every request.
  • RBAC - role-based access control: rights come from the user's role.
  • ABAC - attribute-based access control: rights come from attributes (department, location, number of posts).
  • CSRF - cross-site request forgery: a hostile page makes your browser send a request you did not intend.
  • XSS - cross-site scripting: attacker's script runs in your page.
  • SQL injection - attacker's text becomes part of your SQL command.
  • HTTPS / TLS - HTTP carried inside an encrypted, authenticated channel.
  • Token - a signed string the client sends (for example in a header) instead of a cookie session.
  • Least privilege - give each user and program only the access it needs.

Idea 1: Threats and the two big questions

The OWASP Top 10 is the well-known list of the most common web application weaknesses. You do not need to memorise it; know the family: broken access control, cryptographic failures (plain-text passwords, no HTTPS), injection (SQL, XSS), insecure design, security misconfiguration (debug mode on, default passwords), vulnerable components, authentication failures, logging failures. Attacks come from the browser side (the user, or a script in their page) and from the network (someone reading the wire).

The idea

Authentication = who are you? Authorisation = what may you do? Authentication comes first. Wrong credentials: 401 Unauthorized (really "unauthenticated"). Known user, not allowed: 403 Forbidden. Some apps return 404 for private things to hide that they exist. Mnemonic: 401 means log in, 403 means you are logged in but not allowed.

Access control models.

ModelDecision based onExample
RBACthe user's role"admin can delete courses", "student can only view"
ABACattributes of user, resource, contexta forum unlocks image posting after 25 posts; a cloud service allows files by department, job title or location
Owner-based (discretionary)the owner decidesfile owner shares a document

Real paper answers: "a forum unlocks images once a user makes more than 25 posts" is ABAC; "access by job role in an organisation" is RBAC; "grants access by department, job title or current location" is ABAC.

HTTP authentication schemes (a real match-the-column):

MechanismOne-line fact
HTTPSan encrypted channel
Cookiesmust be sent with each request (the browser does it automatically)
Digest authenticationuses a hash function on the password, so the password is not sent in clear
HTTP Basic authenticationusername:password sent base64-encoded, which is encoding, not encryption, so it is only safe over HTTPS

Do not mix: base64 can be decoded by anyone; a hash cannot be reversed; encryption can be reversed with a key.

Watch out - real exam trap

A real question gave a login form with a wrong-password path that shows nothing. The heuristic broken is visibility of system status (the user should always be told what is happening). Also note: a form with no method uses GET, so a username and password would appear in the URL and the server logs. Login forms must use method="post".

Idea 2: Passwords: hash, salt, never store plain text

If you store passwords as text, anyone who reads your database (a leak, a backup, a curious admin) learns every password, and people reuse passwords. So store only a hash.

Why not encryption? Encryption can be reversed by whoever holds the key, and the key sits on the same server. A hash cannot be reversed: you hash what the user types at login and compare the two hashes.

Why a salt? The same password always gives the same hash, so an attacker can precompute a huge table of hashes of common passwords (a rainbow table) and look yours up. A salt is random data added before hashing, stored with the hash, so two users with the same password get different hashes and precomputed tables are useless. Good password hashes (bcrypt, scrypt, PBKDF2, Argon2) are also deliberately slow, to make guessing expensive. A plain fast hash such as SHA-256 on its own is a poor password hash.

from werkzeug.security import generate_password_hash, check_password_hash
h1 = generate_password_hash("secret")
h2 = generate_password_hash("secret")
print(h1 == h2)                          # False (different random salts)
print(check_password_hash(h1, "secret")) # True
print(check_password_hash(h1, "Secret")) # False
print(h1[:12], len(h1))                  # scrypt:32768:8:1$...  (length 162 on our machine)

We ran it. The stored string contains the method, its settings, the salt and the hash, so check_password_hash needs only the stored string and the typed password. Older Werkzeug versions start with pbkdf2:sha256:...; both are fine. bcrypt (a separate library) works the same way: bcrypt.hashpw(pw, bcrypt.gensalt()) and bcrypt.checkpw(pw, hash).

Solved example: 1. Safe registration and login
# register
user = User(name=name, pw=generate_password_hash(password))   # store the HASH
db.session.add(user); db.session.commit()

# login
user = User.query.filter_by(name=name).first()
if user and check_password_hash(user.pw, password):
    login_user(user)
else:
    return "bad credentials", 401        # same message for 'no such user' and 'wrong password'

Never compare with user.pw == password. Use the same error message for unknown user and wrong password, so attackers cannot learn which usernames exist.

Watch out - real exam trap

Real questions store passwords as plain columns (password = db.Column(db.String, nullable=False)) just to test SQLAlchemy. That is fine for an exercise; in your project you store the hash. Also watch the classic logic bug below.

for vu in valid_users:
    if vu["uid"] == args["uid"] and vu["pwd"] == args["pwd"]:
        return "You are authorized!!"
    else:
        return "You are unauthorized!!"      # else is INSIDE the loop

For uid=professor&pwd=54321 the first record is student, the test fails, the else returns at once, and the second record is never checked: the output is You are unauthorized!! (We ran this.) A real paper asked exactly this, and the credentials in the URL are also a security fault (query strings are logged).

Idea 3: Cookies and sessions

HTTP is stateless: each request is separate. To "remember" a user, the server gives the browser a session id in a cookie; the browser sends it back every time.

Server response:  Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
Next request:     Cookie: session=abc123

Both statements in a real question were true: cookies let a web app recognise users, and they can hold login data, language, location and similar information.

Cookie flagMeaningProtects against
HttpOnlyJavaScript cannot read it (document.cookie)XSS stealing the session id
Securesent only over HTTPSsniffing on the network
SameSite=Lax/Strictnot sent on most cross-site requestsCSRF
Max-Age / Expireslifetimelong-lived stolen cookies

Flask's session. Flask keeps the session in the cookie itself, as a signed (not encrypted) value. The signature uses SECRET_KEY, so the user cannot change it without detection, but the user can read it. We decoded one: the cookie held {"role":"general","user":"a"} in readable base64. So: never store secrets in session, and keep the SECRET_KEY secret. Setting SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE, SESSION_COOKIE_SAMESITE configures the flags.

Solved example: 2. Predict the status codes

Question. (Real End Term, rebuilt and run.)

app.config['SECRET_KEY'] = "yekterces"

@app.route('/login')
def log_in():
    user = request.args['user']
    role = request.args['role'] if 'role' in request.args else 'general'
    session['user'], session['role'] = user, role
    return "Logged in successfully!"

@app.route('/home')
def land():
    if 'user' in session:
        if session['role'] == 'admin':
            return f"Welcome {session['user']}"
        return abort(401)
    return abort(404)

@app.route('/logout')
def log_out():
    session.pop('user', None); session.pop('role', None)
    return "Logged out sucessfully!"

Visit in order: /home, /login?user=a&role=general, /home, /login?user=a&role=admin, /home, /logout, /home.

Solution. 404, 200, 401, 200, 200, 200, 404. No session yet: 404. Logged in as a general user: 401. Logged in as admin: 200 and Welcome a. After logout the keys are gone: 404 again. The browser (client) keeps the cookie between visits; that is why the same URL changes its answer.

Session memory arithmetic. A server-side session store with 1.5 million active sessions of 4 KB each needs 1.5 million x 4 KB = 6 GB of RAM (this exact calculation appeared in a paper; see week 7). It also shows why server sessions need a shared store (Redis) once you scale out.

Idea 4: Flask-Login and roles

Flask-Login manages "who is logged in" for you. Four ingredients:

from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user

login_manager = LoginManager(app)
login_manager.login_view = "login"           # where to send anonymous users

class User(UserMixin, db.Model):             # UserMixin adds is_authenticated etc.
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True)
    pw = db.Column(db.String)
    role = db.Column(db.String, default="user")

@login_manager.user_loader
def load_user(user_id):                      # called on each request with the id from the session
    return db.session.get(User, int(user_id))
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        u = User.query.filter_by(name=request.form["name"]).first()
        if u and check_password_hash(u.pw, request.form["pw"]):
            login_user(u)                    # the USER OBJECT, not user.id, not user.name
            return redirect("/dash")
        return "bad credentials", 401
    return render_template("login.html")

@app.route("/dash")
@login_required                              # anonymous users are redirected to the login page
def dash():
    return "Welcome " + current_user.name

@app.route("/logout")
def logout():
    logout_user(); return "bye"

We ran all of this with the test client. Observed: anonymous /dash gives a 302 redirect to /login?next=%2Fdash (not 404, not 403); a wrong password gives 401; a right one gives a 302 to /dash and a session=... cookie; then /dash returns Welcome b. current_user is the logged-in User object (an anonymous placeholder if nobody is logged in).

Two real questions here: the correct registration call is login_user(user) (the object), and a real snippet that called login_user(u1) for Rose then logout_user() printed, in order, Rose logged in, current user is Rose, logged out when visited as /, /home, /logout.

Roles (RBAC). With Flask-Security you attach roles to users and write @roles_required('admin') (all roles needed) or @roles_accepted('admin', 'editor') (any one). With plain Flask-Login you can write the decorator yourself:

def roles_required(*roles):
    def deco(fn):
        @wraps(fn)
        @login_required
        def inner(*a, **k):
            if current_user.role not in roles:
                return "forbidden", 403          # authenticated but not authorised
            return fn(*a, **k)
        return inner
    return deco

@app.route("/admin")
@roles_required("admin")
def admin(): return "admin area"

Tested: user b (role user) gets 403 on /admin; user a (role admin) gets admin area. A user can have many roles; model it with a many-to-many table (roles in a real paper are a Role table with a relationship back to User).

In your project

Protect every route that changes data. In the viva be ready to show @login_required on the dashboard, a role check on the admin pages, and the line where you hash the password. Also explain what the user_loader does: "Flask-Login stores only the id in the session; this function turns the id back into the user object on each request."

Idea 5: CSRF, XSS and SQL injection

CSRF (cross-site request forgery)

You are logged in to your bank. In another tab you open a hostile page that contains a hidden form posting to bank.com/transfer. Your browser attaches your cookie automatically, so the bank thinks you sent it. Defences:

  • CSRF token: a random secret value put in each form (<input type="hidden" name="csrf" ...>) and kept in the session. The server accepts a POST only if the submitted token equals the session's. The hostile page cannot read your page, so it cannot know the token.
  • SameSite cookies, and never change data with GET.
  • Flask-WTF does this for you (CSRFProtect(app) and {{ form.csrf_token }}). A manual version we tested:
def token():
    if "csrf" not in session: session["csrf"] = secrets.token_hex(16)
    return session["csrf"]

@app.post("/pay")
def pay():
    if request.form.get("csrf") != session.get("csrf"): abort(403)
    return "paid"

Result: no token gives 403, a wrong token gives 403, the right token gives paid.

XSS (cross-site scripting)

If user-written text is placed into a page as HTML, an attacker's script runs in the victim's browser (it can steal data, act as the user). Defence: escape output. Jinja autoescape (on for .html templates) turns < into &lt;:

render_template_string("<p>{{ x }}</p>", x="<script>alert(1)</script>")
#  <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>        (safe: shown as text)
render_template_string("<p>{{ x|safe }}</p>", x="<script>alert(1)</script>")
#  <p><script>alert(1)</script></p>                    (dangerous: runs)

Never use |safe or Markup() on user input. On the JS side use textContent, not innerHTML (week 8). HttpOnly cookies and a Content-Security-Policy header limit the damage.

SQL injection

If you build SQL by gluing strings, the user's input can change the meaning of the query. We ran it:

name = "asha"; pw = "' OR '1'='1"
q = f"select * from u where name='{name}' and pw='{pw}'"
# select * from u where name='asha' and pw='' OR '1'='1'      -> returns the row: login bypassed!

Another payload, asha' --, turns the rest of the line into a comment. Fix: parameterised queries, where the database receives the SQL and the values separately, so values are always data, never code:

cur.execute("select * from u where name=? and pw=?", (name, pw))   # returned [] for the same attack

The ORM is safe by default: User.query.filter_by(name=n) and User.name == n always bind parameters (our attack string returned no user). It becomes unsafe again if you build raw SQL with f-strings in db.session.execute(text(f"...")); use text("... :n") with parameters.

Watch out - real exam trap
  • Escaping fixes XSS, parameters fix SQL injection, tokens fix CSRF. Do not mix the three.
  • |safe on user input disables Jinja's protection.
  • A GET route that deletes or pays is a CSRF gift.
  • Client-side checks (JS, required) are never a defence against injection; validate and escape on the server.

Idea 6: Transport, secrets, validation, limits, tokens

HTTPS and TLS. HTTPS is HTTP inside TLS, which gives encryption (nobody on the way can read), integrity (nobody can alter) and authentication of the server (a certificate from a trusted authority proves the site is the real bank.com). It protects passwords, cookies and form data on the wire. It does not fix XSS or SQL injection. Use the Secure cookie flag with it.

Secrets and environment variables. SECRET_KEY, database passwords and API keys must not be written in the code or pushed to Git. Read them from the environment:

import os
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]   # fails loudly if missing

Keep a local .env (listed in .gitignore) and a .env.example without real values. Anyone who learns the SECRET_KEY can forge Flask sessions.

Input validation. Treat all input as hostile: check type, length, range and format on the server (and on the client for comfort). Prefer an allow list (only letters and digits) to a block list. A real End Term: frontend validation improves user experience, backend validation is mandatory for security because the frontend can be bypassed, and the backend should check the same constraints.

Rate limiting. Limit how many requests an address or user may make in a time window, to slow brute force password guessing and abuse (Flask-Limiter: @limiter.limit("5 per minute")). Our small hand-made version let 3 requests through and refused the 4th and 5th: [True, True, True, False, False]. Return 429 Too Many Requests.

Token-based authentication (introduction). Instead of a server-side session and a cookie, the server gives a signed token (for example a JWT, or a random string). The client sends it in every request: Authorization: Bearer <token>. The server only verifies the signature, so it keeps no session state (stateless, good for scaling) and the same token works for a mobile app or JavaScript calling your REST API. Tokens need an expiry and HTTPS; if stored in localStorage they can be stolen by XSS. You will use token auth with the REST API in MAD2.

Principle of least privilege. Give every person and program the smallest access that lets it do its job: the web app's database user should not be able to drop tables; a normal user gets the user role, not admin; an API key can be read-only. If something is compromised, the damage is small.

Idea 7: A secure project checklist

AreaDo this
Passwordsgenerate_password_hash, never plain text, one error message for login failure
Sessionslong random SECRET_KEY from the environment; HttpOnly, Secure, SameSite; logout clears the session
Access@login_required plus a role check on every sensitive route; check ownership (a user may edit only their rows)
FormsCSRF token on every POST; POST (never GET) to change data
Outputkeep Jinja autoescape on; no safe filter on user text; textContent in JS
DatabaseORM or ? parameters, never f-string SQL; least-privilege DB user
TransportHTTPS in production; debug mode off (the debugger page can run code)
Inputvalidate type, length and format on the server; limit upload size and type
Abuserate-limit login; log failures (week 7 logging) without logging passwords
Secretsenvironment variables, .env in .gitignore

Your turn

Your turn: 1. Hash facts

For each: true or false. (a) A hash can be reversed with the right key. (b) Two users with the same password get the same stored string when salted. (c) check_password_hash needs only the stored string and the typed password.

Need a hint?

Think of what a salt does and what makes hashing different from encryption.

Show the full solution

(a) False (a hash is one-way; keys belong to encryption). (b) False (different salts give different strings). (c) True (method, salt and hash are inside the stored string).

Your turn: 2. Which control?

Name the access-control type: (a) only users with role teacher may open /grades; (b) a file service allows access only when user.country == file.country and time is working hours; (c) a user who has made 25 comments may upload images.

Need a hint?

Roles or attributes?

Show the full solution

(a) RBAC. (b) ABAC. (c) ABAC (an attribute: the number of posts).

Your turn: 3. Status codes

A route has @login_required. What is returned to (a) an anonymous user, (b) a logged-in non-admin user on a route that checks current_user.role == 'admin' and returns abort(403), (c) a request with a wrong CSRF token that the code aborts with 403?

Need a hint?

Anonymous means not authenticated.

Show the full solution

(a) a 302 redirect to the login page (with next). (b) 403 Forbidden. (c) 403.

Your turn: 4. Find the vulnerability

Name the attack and the fix for each. (a) cur.execute(f"select * from users where id = {uid}") (b) {{ comment|safe }} for user comments (c) a "delete my account" link that is a plain GET /delete.

Need a hint?

Injection, injection, forgery.

Show the full solution

(a) SQL injection; use cur.execute("... where id = ?", (uid,)) or the ORM. (b) XSS; remove |safe and keep autoescape. (c) CSRF and unsafe use of GET; use POST with a CSRF token.

Your turn: 5. Memory arithmetic

A store keeps 2 million sessions of 3 KB each in memory (1 KB = 1000 B). How much RAM at minimum?

Need a hint?

Multiply, then convert bytes to GB.

Show the full solution

2,000,000 x 3000 B = 6,000,000,000 B = 6 GB.

Exam radar

Exam radar

About 18 of ~692 Quiz 1, about 36 of ~624 Quiz 2 and about 50 of ~1600 End Term questions match this week's keywords (rough counts, and many of those matches are really neighbouring topics such as SQLAlchemy models, forms and Flask routes). In the sample of about 27 questions the genuinely security ones are fewer:

  • Flask-Login (login_user(user), @login_required redirecting to the login page, user_loader and UserMixin, current_user) and Flask sessions with status codes (404/401/200 sequences).
  • Access control types: ABAC vs RBAC (twice) and the match-the-column on HTTPS, cookies, digest and basic authentication.
  • Cookie statements (recognise users, can hold login data).
  • Frontend (HTML5/JS) vs backend validation; the form method default (GET).
  • Login logic with credentials in a URL (the for/else trap) and session memory arithmetic (1.5 million sessions of 4 KB).
  • Many login-themed questions are really SQLAlchemy ORM tests (insert a user, filter_by(...).first(), relationships between User and Role), taught in earlier weeks.

I did not find questions on CSRF, rate limiting or JWT in this sample, so learn them for the viva and as concept; they are more likely in MAD2 and in project discussions.

Watch out - real exam trap
  • login_user(user), not user.id, not user.name.
  • 404 vs 401 vs 403 in session-based routes: read which if the code reaches.
  • A for ... else or an early return inside a loop can reject valid users.
  • Base64 is encoding, not encryption; a hash is not encryption; a salt is not secret.
  • Frontend checks never replace backend checks.

Quick quiz

Quick quiz

Which flask_login call registers the user as logged in for the session?

  • login_user(user.id)
  • login_user(user)
  • login_user(user.name)
  • login_user(user.name and user.password)
login_user takes the user object; user_loader later turns the stored id back into it.

An anonymous visitor opens a route decorated with @login_required. What happens by default?

  • 404
  • the page shows "Welcome None"
  • a redirect to the login page
  • 403 error page
Flask-Login redirects to login_manager.login_view (with a next parameter).

A forum unlocks image posting once a user has more than 25 posts. This is...

  • role-based access control
  • attribute-based access control
  • both
  • neither
The rule depends on an attribute (post count), not on a role.

Select all TRUE statements about passwords. (select all that apply)

  • Store a salted hash, not the password.
  • The salt makes equal passwords produce different hashes.
  • Base64 encoding is a safe way to store passwords.
  • Hashing is reversible if you know the key.
Base64 is trivially decoded; hashing is one-way; keys belong to encryption.

Match: HTTPS, Cookies, Digest authentication, HTTP Basic authentication.

  • HTTPS: base64; Cookies: hash; Digest: encrypted channel; Basic: sent each time
  • HTTPS: encrypted channel; Cookies: sent with each request; Digest: hash function; Basic: base64 encoding
  • HTTPS: hash function; Cookies: encrypted channel; Digest: base64; Basic: sent each time
  • HTTPS: sent each time; Cookies: base64; Digest: encrypted channel; Basic: hash
These are the standard associations from the paper.

Which defence stops SQL injection?

  • CSRF tokens
  • HttpOnly cookies
  • parameterised queries or the ORM
  • Jinja autoescape
Parameters keep user input as data; the other options address different attacks.

Viva: say it out loud

Viva: say it out loud
How do you store passwords in your project and why?

I store only a salted hash using generate_password_hash, and at login I use check_password_hash. Hashing is one-way, so a leaked database does not reveal passwords, and the salt stops rainbow tables and makes equal passwords differ. I do not use encryption because the key would sit next to the data.

Explain authentication and authorisation in your app.

Authentication is checking who the user is at login with the username and password. Authorisation is checking what they may do: @login_required for logged-in pages and a role check, for example admin, on sensitive routes. Not logged in gives a redirect to login; logged in but not allowed gives 403.

How does your login stay active between requests?

After a successful login Flask-Login stores the user id in Flask's signed session cookie. On every request the user_loader function loads the user from that id. The cookie is HttpOnly and signed with my SECRET_KEY, which I keep in an environment variable.

What is CSRF and how do you stop it?

It is when another site makes the user's browser send a request to my site with the user's cookies. I stop it with a secret CSRF token in each form that the server checks, with SameSite cookies, and by using POST for any change.

What is XSS and how does Flask protect you?

It is injecting a script that runs in another user's browser. Jinja autoescape turns < and > into entities in templates, so user text is shown, not run. I never use |safe on user input and I use textContent in JavaScript.

How does SQL injection work and how did you avoid it?

If user input is glued into the SQL string, an attacker can add their own SQL, like ' OR '1'='1. I use SQLAlchemy queries, which pass values as parameters, so input stays data.

Why use HTTPS and environment variables?

HTTPS encrypts and authenticates traffic, so passwords and cookies cannot be read or changed on the network. Environment variables keep secrets such as SECRET_KEY out of the source code and Git history.

What is the principle of least privilege?

Each user, process and database account gets only the access it needs. So a normal user is not an admin and the app's database account cannot drop tables, which limits damage if something is compromised.

Recap

Remember this
  • Authentication = who; authorisation = what. 401 means log in, 403 means not allowed. RBAC by role, ABAC by attributes.
  • Passwords: store a salted slow hash (generate_password_hash / check_password_hash); never plain text; hash is not encryption; salt defeats rainbow tables.
  • Sessions and cookies: cookie carries the session; flags HttpOnly, Secure, SameSite; Flask session is signed, not encrypted; keep SECRET_KEY secret.
  • Flask-Login: login_user(user), logout_user(), @login_required (redirects), user_loader, current_user; roles with @roles_required.
  • Attacks and cures: CSRF to token and SameSite; XSS to escaping (autoescape, no |safe, textContent); SQL injection to parameters or the ORM.
  • Hardening: HTTPS/TLS, secrets in environment variables, server-side validation, rate limiting, least privilege, debug off in production.
  • Tokens (Authorization: Bearer ...) give stateless authentication for APIs; used more in MAD2.
Code and facts to keep
  • generate_password_hash(pw) and check_password_hash(stored, pw) from werkzeug.security
  • login_user(user), logout_user(), @login_required, current_user, @login_manager.user_loader
  • @roles_required('admin') (all), @roles_accepted('a','b') (any)
  • session['k'] = v, session.pop('k', None), app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
  • SESSION_COOKIE_HTTPONLY = True, SESSION_COOKIE_SECURE = True, SESSION_COOKIE_SAMESITE = "Lax"
  • Cookie header: Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
  • Parameterised SQL: cur.execute("select * from u where name=?", (n,))
  • Jinja: {{ x }} escapes; {{ x|safe }} does not
  • Status codes: 401 unauthenticated, 403 forbidden, 404 not found, 429 too many requests, 302 redirect
  • Basic auth = base64 (not encryption); Digest = hash; HTTPS = encrypted channel
Connected weeks (learn them together)

Practise with real exam questions

These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week   All End Term papers