Skip to content

Latest commit

 

History

History
214 lines (161 loc) · 6.22 KB

File metadata and controls

214 lines (161 loc) · 6.22 KB

Lab: Flask + Jinja2 Web App with JWT Auth (60–90 min)

Overview

In this lab, you'll build a Flask web app with Jinja2 templates that supports user login, dashboard, and logout using JWT HttpOnly cookies. You'll keep your existing API-key CRUD endpoints unchanged, but add a simple web interface for interactive login and session management using Jinja2.


What is Jinja2? (Templating Basics)

  • Jinja2 is the default templating engine for Flask. It lets you write HTML files with special placeholders and logic that Flask fills in with real data.
  • Templates are just HTML files with extra syntax for variables, loops, and conditionals.

Variables

  • Use {{ variable }} to insert a value:
    <p>Hello, {{ username }}!</p>

If/Else

  • Use {% if ... %} ... {% else %} ... {% endif %} for conditional logic:
    {% if error %}
      <p style="color:red">{{ error }}</p>
    {% else %}
      <p>Welcome!</p>
    {% endif %}

Loops

  • Use {% for item in list %} ... {% endfor %} to loop over data:
    <ul>
      {% for key in api_keys %}
        <li>{{ key.value }} ({{ key.permissions|join(', ') }})</li>
      {% endfor %}
    </ul>

Template Inheritance (extends)

  • Use {% extends "layout.html" %} to base a template on another (like a master page).
  • Use {% block content %} ... {% endblock %} to define sections that child templates can fill in.

Example

<!-- login.html -->
{% extends "layout.html" %}
{% block content %}
<h2>Login</h2>
<form method="post">
    <label>Username: <input name="username"></label><br>
    <label>Password: <input name="password" type="password"></label><br>
    <button type="submit">Login</button>
</form>
{% if error %}<p style="color:red">{{ error }}</p>{% endif %}
{% endblock %}

Jinja2 makes it easy to build dynamic web pages by mixing Python data and logic into your HTML templates!


Folder Structure

project/
├── app.py
├── templates/
│   ├── login.html
│   ├── dashboard.html
│   └── layout.html
└── static/
    └── (optional CSS)

Templates

layout.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title or "Flask JWT Lab" }}</title>
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>

login.html

{% extends "layout.html" %}
{% block content %}
<h2>Login</h2>
<form method="post">
    <label>Username: <input name="username"></label><br>
    <label>Password: <input name="password" type="password"></label><br>
    <button type="submit">Login</button>
</form>
{% if error %}<p style="color:red">{{ error }}</p>{% endif %}
{% endblock %}

dashboard.html

{% extends "layout.html" %}
{% block content %}
<h2>Welcome, {{ username }}!</h2>
<p>Your API keys and permissions:</p>
<ul>
    {% for key in api_keys %}
    <li>{{ key.value }} ({{ key.permissions|join(', ') }})</li>
    {% endfor %}
</ul>
<a href="/logout">Logout</a>
{% endblock %}

Flask App (app.py) – Jinja2 Focus Only

from flask import Flask, render_template, request

app = Flask(__name__)

api_keys = [
    {'value': 'key1', 'permissions': ['read']},
    {'value': 'key2', 'permissions': ['read', 'write']}
]

@app.route('/dashboard')
def dashboard():
    username = request.args.get('username', 'alice')
    return render_template('dashboard.html', username=username, api_keys=api_keys)

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        # Dummy check for demonstration
        if username == 'alice' and password == 'password1':
            return render_template('dashboard.html', username=username, api_keys=api_keys)
        else:
            error = 'Invalid credentials'
    return render_template('login.html', error=error)

# ...other endpoints as needed for your app...

if __name__ == '__main__':
    app.run(debug=True)

CSRF (Cross-Site Request Forgery)

CSRF stands for Cross-Site Request Forgery. It is an attack where a malicious website tricks a user’s browser into sending an unintended request to another site where the user is already authenticated.

Because browsers automatically include cookies with every request to a site, an attacker does not need to steal the cookie. If the victim is logged in, the browser will send the authentication cookie along with the forged request.

In this lab:

  • Authentication is stored in an HttpOnly JWT cookie.
  • Forms submit POST requests from the browser.
  • The browser will automatically attach the JWT cookie.

This means an attacker could potentially cause actions (like logout or data changes) without the user’s intent.

Important:

HttpOnly protects cookies from being read by JavaScript (XSS).

It does not protect against CSRF.

Real-world web applications protect against CSRF by requiring a CSRF token: a secret value that must be included in each form submission and validated by the server.


If you want to use HTTPS, you must use port 443 and create a virtual env to run as root eg. sudo venv/bin/python app.py. Do not forget to open the SG. Your browser will not trust the certificate.

Challenge

  • Add a page to allow users to create and manage API keys from the dashboard (form + POST handler).
  • Make sure only logged-in users can access this page.
  • Add a way to delete/revoke API keys from the dashboard.
  • Make sure the API keys still work via CURL on the API key operations your original application has.

Bonus Challenge (Optional): CSRF Protection

Add simple CSRF protection to your web forms:

Generate a random CSRF token on each GET request for the login and dashboard pages. Store the token server-side (or in a signed cookie). Include the token as a hidden field in the HTML form:

Verify the token on POST before processing the request.

Reject the request if the token is missing or invalid.

This ensures that only forms served by your application can successfully submit authenticated requests.


This lab will help you build a secure Flask web app with Jinja2 templates, JWT-based authentication and CSRF protection!