Learning Python - Security Best Practices
Series/Learn Python/Episode 14
Episode 14 of 23

Learning Python - Security Best Practices

This episode secures your application: secure coding with input validation and escaping, SQL injection prevention, authentication and authorization patterns with JWT and OAuth2, CSRF and CORS protection, plus dependency security scanning and supply-chain risks.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Security isn't a feature added at the end — it's a design decision at every layer. Episode 14 equips you with the security best practices adopted by production teams: writing secure code, preventing injection, securing authentication, and safeguarding the dependency chain.

We'll follow OWASP principles, discuss JWT and OAuth2, and cover CSRF and CORS. The goal is one thing: your API shouldn't be easily exploited by the most common attacks.

Secure Coding: Validation and Escaping

The Trust Boundary Principle

The first rule of security: never trust user input. Every input crosses a trust boundary and must be validated. FastAPI with pydantic already helps:

PythonValidasi input otomatis
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr, Field
 
app = FastAPI()
 
class Pengguna(BaseModel):
    email: EmailStr
    usia: int = Field(ge=13, le=120)
 
@app.post("/pengguna")
def buat_pengguna(data: Pengguna):
    return {"email": data.email}

EmailStr validates the email format and Field(ge=13, le=120) limits the age range. Validation at the input layer prevents dangerous data from penetrating deeper into the system. Never postpone validation to another layer.

Preventing SQL Injection

Why Injection Is Dangerous

SQL injection happens when user input is concatenated directly into a SQL query:

PythonCara salah: f-string di SQL
nama = "admin' OR '1'='1"
query = f"SELECT * FROM pengguna WHERE nama = '{nama}'"
print("query rentan:", query)

f"SELECT * FROM pengguna WHERE nama = '{nama}'" concatenates input directly into SQL. With the input above, the query always returns every row — a serious data leak. This is a pattern you must avoid.

Using Parameterized Queries

The solution is parameterized queries or bound parameters:

PythonCara aman: parameterized query
from sqlalchemy import create_engine, text
 
engine = create_engine("sqlite:///belajar.db")
 
with engine.connect() as conn:
    hasil = conn.execute(
        text("SELECT * FROM pengguna WHERE nama = :nama"),
        {"nama": "Arman"},
    )
    print(hasil.fetchall())

text("SELECT * FROM pengguna WHERE nama = :nama") uses the :nama placeholder and the value is passed separately in a dict. The database treats the value as data, not as a SQL command. Always use parameterized queries — there's no reason to use f-strings in SQL.

Authentication with JWT

Understanding JWT

JWT (JSON Web Token) is a standard authentication token:

Install PyJWT
pip install pyjwt
PythonMembuat dan memverifikasi JWT
import jwt
from datetime import datetime, timedelta
 
rahasia = "kunci-rahasia-anda"
 
token = jwt.encode(
    {"sub": "user-123", "exp": datetime.now() + timedelta(hours=1)},
    rahasia,
    algorithm="HS256",
)
print(token)
 
decode = jwt.decode(token, rahasia, algorithms=["HS256"])
print(decode["sub"])

jwt.encode(payload, rahasia, algorithm="HS256") creates a token with the sub and exp claims. jwt.decode verifies the signature and expiry. Clients send this token in the Authorization header to access protected endpoints.

OAuth2 and Authorization

OAuth2 for Delegated Access

OAuth2 is a protocol for granting access without sharing passwords. FastAPI has built-in support:

PythonOAuth2 di FastAPI
from fastapi.security import OAuth2PasswordBearer
 
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

OAuth2PasswordBearer(tokenUrl="token") configures the OAuth2 password flow. Clients send credentials to /token, receive an access token, then use it on protected endpoints. For full production needs, consider providers like Auth0 or Keycloak.

CSRF and CORS

Protecting Against CSRF

CSRF forces a user's browser to send unwanted requests. Frameworks provide protection:

Header anti-CSRF
X-CSRFToken: token-unik-per-sesi

CSRF protection generally adds a unique per-session token that must be sent with requests. Django and other frameworks provide this middleware. Always enable it for applications that use cookie authentication.

Configuring CORS

CORS controls which domains may call your API:

PythonMengonfigurasi CORS FastAPI
from fastapi.middleware.cors import CORSMiddleware
 
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://aplikasi-anda.com"],
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization"],
)

CORSMiddleware(allow_origins=["https://aplikasi-anda.com"]) restricts the allowed request origins. Don't use allow_origins=["*"] in production — it opens your API to every domain. Tight CORS configuration reduces the risk of cross-origin attacks.

Dependency Security Scanning

Scanning for Vulnerabilities

The dependencies you install can contain vulnerabilities. Scan regularly with pip-audit:

Install dan jalankan pip-audit
pip install pip-audit
pip-audit

pip-audit scans installed packages against the CVE database and reports found vulnerabilities. Run it locally and in CI/CD to keep dependencies safe over time.

Closing

Key takeaways:

  • Validate all input at the trust boundary with pydantic.
  • Escape output to prevent XSS attacks.
  • Parameterized queries prevent SQL injection.
  • JWT provides stateless authentication with a signature and exp.
  • OAuth2 delegates access without sharing passwords.
  • pip-audit scans dependencies against the CVE database regularly.

In the next episode, episode 15, we'll cover concurrency and parallelism — threading, multiprocessing, and asyncio, their models and pitfalls, modern async patterns with TaskGroups in Python 3.11, plus the trio and anyio alternatives. Your API will run faster and more efficiently!

Learning Python - Security Best Practices | Learn Python