Table of Contents
-
Understanding Authentication Basics
- 1.1 What is Authentication?
- 1.2 Authentication vs. Authorization
- 1.3 Common Authentication Methods
-
Setting Up Your Python Environment
- 2.1 Virtual Environments
- 2.2 Essential Libraries
-
- 3.1 Why Hash Passwords?
- 3.2 Hashing with Bcrypt
- 3.3 Storing Hashes in a Database
- 3.4 Example: Login/Logout with Flask
-
- 4.1 How JWT Works
- 4.2 Implementing JWT with PyJWT
- 4.3 Example: Securing an API with JWT
-
OAuth and Social Authentication
- 5.1 OAuth 2.0 Overview
- 5.2 Example: GitHub OAuth with Flask-Dance
-
Multi-Factor Authentication (MFA)
- 6.1 TOTP with PyOTP
- 6.2 Example: Adding MFA to a Flask App
-
- 7.1 Secure Session Practices
- 7.2 Flask/Django Session Configuration
-
Best Practices for Authentication
- 8.1 Password Policies
- 8.2 Avoiding Common Pitfalls
- 8.3 Rate Limiting and Security Headers
-
- 9.1 Passwordless Authentication
- 9.2 Single Sign-On (SSO) with SAML
1. Understanding Authentication Basics
1.1 What is Authentication?
Authentication is the process of verifying that a user is who they claim to be. It typically involves presenting credentials (e.g., username/password, tokens, or biometrics) that the system validates against stored data.
1.2 Authentication vs. Authorization
- Authentication: “Who are you?” (Verifies identity.)
- Authorization: “What can you do?” (Controls access to resources after identity is confirmed.)
For example, logging into a banking app is authentication; accessing your savings account is authorization.
1.3 Common Authentication Methods
- Password-based: Users provide a username/password.
- Token-based: Users receive a cryptographically signed token (e.g., JWT) after login.
- OAuth/Social: Users authenticate via third parties (e.g., Google, GitHub).
- Multi-factor (MFA): Combines two+ methods (e.g., password + SMS code).
2. Setting Up Your Python Environment
2.1 Virtual Environments
Always use a virtual environment to isolate dependencies:
# Create a virtual environment
python -m venv auth-env
# Activate it (Windows)
auth-env\Scripts\activate
# Activate it (macOS/Linux)
source auth-env/bin/activate
2.2 Essential Libraries
Install these packages for the examples below:
# Core tools
pip install flask django # Web frameworks
pip install bcrypt pyjwt # Hashing/tokens
pip install flask-dance pyotp # OAuth/MFA
pip install sqlalchemy # Database ORM
3. Password-Based Authentication
3.1 Why Hash Passwords?
Never store plaintext passwords—they’re vulnerable to data breaches. Instead, hash passwords with a cryptographic algorithm (e.g., bcrypt) that converts them into irreversible strings.
3.2 Hashing with Bcrypt
Bcrypt is slow by design (to resist brute-force attacks) and includes a built-in salt (a random value added to the password before hashing).
Example: Hashing and Verifying a Password
import bcrypt
# Hash a password
password = "user_secure_password123".encode("utf-8") # Convert to bytes
salt = bcrypt.gensalt() # Generate a salt
hashed_password = bcrypt.hashpw(password, salt) # Hash with salt
# Store `hashed_password` in your database
# Verify a password later
user_input = "user_secure_password123".encode("utf-8")
if bcrypt.checkpw(user_input, hashed_password):
print("Password matches!")
else:
print("Incorrect password.")
3.3 Storing Hashes in a Database
Use an ORM like SQLAlchemy to store hashed passwords. Define a user model with a password_hash field:
from sqlalchemy import Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
password_hash = Column(String(60), nullable=False) # Bcrypt hashes are 60 chars
def set_password(self, password):
self.password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
def check_password(self, password):
return bcrypt.checkpw(password.encode("utf-8"), self.password_hash)
3.4 Example: Login/Logout with Flask
Let’s build a simple login system with Flask and Flask-Login (a session management library).
Step 1: Configure Flask and Flask-Login
from flask import Flask, render_template, redirect, url_for, request
from flask_login import (
LoginManager, UserMixin, login_user, login_required,
logout_user, current_user
)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
app = Flask(__name__)
app.secret_key = "your_secure_secret_key_here" # Required for sessions
# Database setup
engine = create_engine("sqlite:///users.db")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
db_session = Session()
# Flask-Login setup
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login" # Redirect here if login is required
Step 2: Define User Loader and Routes
# User loader for Flask-Login (retrieves user by ID)
@login_manager.user_loader
def load_user(user_id):
return db_session.query(User).get(int(user_id))
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
user = db_session.query(User).filter_by(username=username).first()
if user and user.check_password(password):
login_user(user) # Starts a session
return redirect(url_for("dashboard"))
else:
return "Invalid username or password"
return render_template("login.html")
@app.route("/dashboard")
@login_required # Requires authentication
def dashboard():
return f"Welcome, {current_user.username}! <a href='/logout'>Logout</a>"
@app.route("/logout")
@login_required
def logout():
logout_user() # Ends the session
return redirect(url_for("login"))
if __name__ == "__main__":
app.run(ssl_context="adhoc") # Use HTTPS in production!
4. Token-Based Authentication
4.1 How JWT Works
JSON Web Tokens (JWT) are compact, URL-safe tokens used to authenticate users. They consist of three parts:
- Header: Specifies the signing algorithm (e.g., HS256).
- Payload: Contains claims (e.g., user ID, expiration time).
- Signature: Verifies the token hasn’t been tampered with.
4.2 Implementing JWT with PyJWT
Example: Creating and Verifying a JWT
import jwt
from datetime import datetime, timedelta
# Configuration
SECRET_KEY = "your_jwt_secret_key" # Store this securely (e.g., environment variable)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire}) # Add expiration claim
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def verify_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub") # "sub" is a standard claim for subject (user ID)
if user_id is None:
return None
return user_id
except jwt.PyJWTError:
return None
4.3 Example: Securing an API with JWT
Use JWT to protect API endpoints. Clients include the token in the Authorization header:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/login", methods=["POST"])
def login():
# Validate username/password (omitted for brevity)
user_id = 123 # Retrieve from database
token = create_access_token(data={"sub": user_id})
return jsonify(access_token=token, token_type="bearer")
@app.route("/protected", methods=["GET"])
def protected():
token = request.headers.get("Authorization")
if not token or not token.startswith("Bearer "):
return jsonify({"error": "Unauthorized"}), 401
token = token.split(" ")[1]
user_id = verify_token(token)
if not user_id:
return jsonify({"error": "Invalid token"}), 401
return jsonify(message=f"Welcome, user {user_id}!")
if __name__ == "__main__":
app.run()
5. OAuth and Social Authentication
5.1 OAuth 2.0 Overview
OAuth 2.0 lets users authenticate via third-party services (e.g., Google, GitHub) without sharing their passwords. The flow involves:
- The app redirects the user to the third-party provider.
- The user logs in and grants permissions.
- The provider sends an authorization code to the app.
- The app exchanges the code for an access token to fetch user data.
5.2 Example: GitHub OAuth with Flask-Dance
Flask-Dance simplifies OAuth integration.
Step 1: Register a GitHub OAuth App
- Go to GitHub Developer Settings.
- Create a new OAuth app:
- Homepage URL:
http://localhost:5000 - Authorization callback URL:
http://localhost:5000/login/github/authorized
- Homepage URL:
Step 2: Implement OAuth in Flask
from flask import Flask, redirect, url_for
from flask_dance.contrib.github import make_github_blueprint, github
app = Flask(__name__)
app.secret_key = "your_secret_key"
# Configure GitHub OAuth
github_bp = make_github_blueprint(
client_id="YOUR_GITHUB_CLIENT_ID",
client_secret="YOUR_GITHUB_CLIENT_SECRET",
)
app.register_blueprint(github_bp, url_prefix="/login")
@app.route("/")
def index():
if not github.authorized:
return redirect(url_for("github.login")) # Redirect to GitHub login
resp = github.get("/user") # Fetch user data
return f"Logged in as {resp.json()['login']}!"
if __name__ == "__main__":
app.run()
6. Multi-Factor Authentication (MFA)
6.1 TOTP with PyOTP
Time-Based One-Time Passwords (TOTP) generate 6-digit codes that expire after 30 seconds (used by Google Authenticator). PyOTP implements TOTP.
6.2 Example: Adding MFA to a Flask App
Step 1: Generate a TOTP Secret for a User
import pyotp
# Generate a secret for the user (store this in your database)
totp_secret = pyotp.random_base32()
# Create a QR code URL for Google Authenticator
totp = pyotp.TOTP(totp_secret)
qr_code_url = totp.provisioning_uri(
name="[email protected]",
issuer_name="MyApp"
)
# Display this QR code to the user (e.g., with the `qrcode` library)
Step 2: Verify the TOTP Code
# User enters the code from Google Authenticator
user_code = "123456"
# Verify the code
if totp.verify(user_code):
print("MFA verified!")
else:
print("Invalid code.")
7. Session Management
7.1 Secure Session Practices
- Use HTTPS: Encrypt data in transit.
- Secure Cookies: Set
SESSION_COOKIE_SECURE=True(only send over HTTPS). - HTTPOnly Cookies: Prevent XSS attacks by blocking JavaScript access.
- Session Timeout: Expire sessions after inactivity (e.g., 30 minutes).
7.2 Flask Session Configuration
app.config.update(
SESSION_COOKIE_SECURE=True, # Only send over HTTPS
SESSION_COOKIE_HTTPONLY=True, # Block JS access
SESSION_COOKIE_SAMESITE="Lax", # Mitigate CSRF
PERMANENT_SESSION_LIFETIME=timedelta(minutes=30), # Session expiration
)
8. Best Practices for Authentication
8.1 Password Policies
- Enforce minimum length (e.g., 10+ characters).
- Require complexity (e.g., uppercase, numbers, symbols).
- Ban common passwords (e.g., “password123”).
8.2 Avoiding Common Pitfalls
- CSRF: Use Flask-WTF’s CSRF protection or Django’s built-in middleware.
- XSS: Sanitize user input and use Content Security Policy (CSP) headers.
- Brute-Force Attacks: Implement rate limiting (e.g., with
flask-limiter).
8.3 Rate Limiting and Security Headers
Example: Rate Limiting with Flask-Limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["100 per day", "10 per hour"]
)
@app.route("/login")
@limiter.limit("5 per minute") # Limit login attempts
def login():
# ...
9. Advanced Topics
9.1 Passwordless Authentication
Replace passwords with magic links (sent via email) or one-time codes. Use Flask-Mail to send links with JWT tokens.
9.2 Single Sign-On (SSO) with SAML
For enterprise apps, use SAML (Security Assertion Markup Language) to authenticate via identity providers like Okta or Azure AD. Libraries like pysaml2 simplify implementation.
10. Conclusion
Authentication is critical for securing Python applications. By choosing the right method (passwords, tokens, OAuth, or MFA) and following best practices (hashing, HTTPS, rate limiting), you can protect user data and build trust. Always stay updated on security trends and patch dependencies regularly!