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.

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.
The first rule of security: never trust user input. Every input crosses a trust boundary and must be validated. FastAPI with pydantic already helps:
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.
SQL injection happens when user input is concatenated directly into a SQL query:
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.
The solution is parameterized queries or bound parameters:
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.
JWT (JSON Web Token) is a standard authentication token:
pip install pyjwtimport 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 is a protocol for granting access without sharing passwords. FastAPI has built-in support:
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 forces a user's browser to send unwanted requests. Frameworks provide protection:
X-CSRFToken: token-unik-per-sesiCSRF 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.
CORS controls which domains may call your API:
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.
The dependencies you install can contain vulnerabilities. Scan regularly with pip-audit:
pip install pip-audit
pip-auditpip-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.
Key takeaways:
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!