Python Web Authentication
Introduction
Authentication is a critical component of web applications, ensuring that users are who they claim to be. In Python web development, various frameworks and libraries make implementing secure authentication systems straightforward. This guide will walk you through different authentication methods in Python web applications, focusing on practical implementations that you can apply to your projects.
Authentication answers the question "Who are you?" while authorization (which often follows authentication) answers "What are you allowed to do?" In this tutorial, we'll focus primarily on authentication techniques.
Authentication Basics
Before diving into code implementations, let's understand the fundamental concepts of web authentication:
- User Credentials: Typically username/email and password
- Session Management: Maintaining user state across requests
- Password Security: Hashing and salting for secure storage
- Token-based Authentication: Using tokens (like JWT) for stateless authentication
- OAuth/Social Login: Delegating authentication to third-party providers
Password Hashing
Never store passwords in plain text! Always use secure hashing algorithms. Python's werkzeug
library (used by Flask) provides excellent password hashing utilities:
from werkzeug.security import generate_password_hash, check_password_hash
# Creating a hashed password
hashed_password = generate_password_hash('my_secure_password')
print(f"Hashed password: {hashed_password}")
# Verifying a password
is_correct = check_password_hash(hashed_password, 'my_secure_password')
print(f"Password verification result: {is_correct}") # True
# Incorrect password check
is_correct = check_password_hash(hashed_password, 'wrong_password')
print(f"Incorrect password verification: {is_correct}") # False
Output:
Hashed password: pbkdf2:sha256:260000$g1EQNgFGZdTKJdru$9e73e77bdc2ecf3559c4e3f597e9903ec588e3609deffa5c3c57fdef1e040147
Password verification result: True
Incorrect password verification: False
Flask Authentication with Flask-Login
Flask-Login is a popular extension for handling user sessions in Flask applications.
Step 1: Installation
pip install flask flask-login
Step 2: Configure Flask-Login
from flask import Flask, request, render_template, redirect, url_for, flash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # In production, use a secure random key
# Initialize Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
# Simple user model (in production, use a database)
class User(UserMixin):
def __init__(self, id, username, password_hash):
self.id = id
self.username = username
self.password_hash = password_hash
# Mock database
users = {
1: User(1, 'user1', generate_password_hash('password1')),
2: User(2, 'user2', generate_password_hash('password2'))
}
# User loader function for Flask-Login
@login_manager.user_loader
def load_user(user_id):
return users.get(int(user_id))
Step 3: Implement Login and Logout Routes
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# Find user by username
user = next((u for u in users.values() if u.username == username), None)
if user and check_password_hash(user.password_hash, password):
login_user(user)
return redirect(url_for('dashboard'))
else:
flash('Invalid username or password')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/dashboard')
@login_required
def dashboard():
return f'Welcome to your dashboard, {current_user.username}!'
@app.route('/')
def home():
return 'Welcome to the Flask Authentication Demo!'
Step 4: Create a Simple Login Template
Create a file named login.html
in a templates
folder:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<form method="post">
<div>
<label>Username:</label>
<input type="text" name="username" required>
</div>
<div>
<label>Password:</label>
<input type="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
</body>
</html>
Token-Based Authentication with JWT
JSON Web Tokens (JWT) provide a stateless authentication mechanism, ideal for APIs and SPAs.
Step 1: Installation
pip install flask pyjwt
Step 2: Implementing JWT Authentication
from flask import Flask, jsonify, request
import jwt
import datetime
from functools import wraps
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-jwt-key' # Use a secure key in production
# Example user database
users = {
'user1': 'password1',
'user2': 'password2'
}
# Token verification decorator
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
# Check if token is in the header
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
if auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
# Decode the token
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
current_user = data['username']
except:
return jsonify({'message': 'Token is invalid!'}), 401
return f(current_user, *args, **kwargs)
return decorated
@app.route('/login', methods=['POST'])
def login():
auth = request.json
if not auth or not auth.get('username') or not auth.get('password'):
return jsonify({'message': 'Could not verify'}), 401
if auth.get('username') in users and users[auth.get('username')] == auth.get('password'):
# Generate token
token = jwt.encode({
'username': auth.get('username'),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}, app.config['SECRET_KEY'], algorithm="HS256")
return jsonify({'token': token})
return jsonify({'message': 'Invalid credentials'}), 401
@app.route('/protected', methods=['GET'])
@token_required
def protected(current_user):
return jsonify({'message': f'Hello, {current_user}! This is a protected endpoint.'})
Step 3: Testing JWT Authentication
You can test the JWT implementation using a tool like curl or Postman:
- First, obtain a token:
curl -X POST -H "Content-Type: application/json" -d '{"username":"user1","password":"password1"}' http://localhost:5000/login
- Then use the token to access a protected endpoint:
curl -X GET -H "Authorization: Bearer YOUR_TOKEN_HERE" http://localhost:5000/protected
OAuth Authentication
OAuth allows users to authenticate using third-party services like Google, Facebook, or GitHub.
Example: GitHub OAuth with Flask
Step 1: Installation
pip install flask requests
Step 2: Implementation
from flask import Flask, redirect, request, session, url_for
import requests
import os
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
# GitHub OAuth settings (replace with your own)
GITHUB_CLIENT_ID = 'your-github-client-id'
GITHUB_CLIENT_SECRET = 'your-github-client-secret'
GITHUB_REDIRECT_URI = 'http://localhost:5000/callback'
@app.route('/')
def index():
if 'github_token' in session:
return f'Logged in as {session["user_data"]["login"]} <br> <a href="/logout">Logout</a>'
return 'Not logged in <br> <a href="/login">Login with GitHub</a>'
@app.route('/login')
def login():
# Create authorization URL for GitHub
github_auth_url = f'https://github.com/login/oauth/authorize?client_id={GITHUB_CLIENT_ID}&redirect_uri={GITHUB_REDIRECT_URI}'
return redirect(github_auth_url)
@app.route('/callback')
def callback():
# Get the code from the callback
code = request.args.get('code')
# Exchange code for access token
response = requests.post(
'https://github.com/login/oauth/access_token',
data={
'client_id': GITHUB_CLIENT_ID,
'client_secret': GITHUB_CLIENT_SECRET,
'code': code,
'redirect_uri': GITHUB_REDIRECT_URI
},
headers={'Accept': 'application/json'}
)
# Get the access token
token_data = response.json()
access_token = token_data.get('access_token')
# Use the token to get user info
user_response = requests.get(
'https://api.github.com/user',
headers={'Authorization': f'token {access_token}'}
)
# Store user info in session
session['github_token'] = access_token
session['user_data'] = user_response.json()
return redirect(url_for('index'))
@app.route('/logout')
def logout():
session.pop('github_token', None)
session.pop('user_data', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
To use this example:
- Register a new OAuth application in your GitHub account settings
- Replace
GITHUB_CLIENT_ID
andGITHUB_CLIENT_SECRET
with your own values - Ensure the redirect URI in GitHub matches the one in your code
Django Authentication System
Django comes with a built-in authentication system that's robust and easy to use.
Basic Django Authentication Setup
# settings.py (relevant parts)
INSTALLED_APPS = [
# ...
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# ...
]
Creating Login Views
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('login')
@login_required
def dashboard(request):
return render(request, 'dashboard.html', {'user': request.user})
URLs Configuration
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('dashboard/', views.dashboard, name='dashboard'),
]
Security Best Practices
- HTTPS: Always use HTTPS in production to encrypt data in transit
- CSRF Protection: Implement CSRF tokens in forms (built-in with Django and Flask-WTF)
- Strong Password Policies: Enforce complex passwords
- Rate Limiting: Prevent brute force attacks by limiting login attempts
- Two-Factor Authentication: Add an extra layer of security
- Keep Dependencies Updated: Regularly update your packages to patch security vulnerabilities
- Secure Headers: Implement security headers to prevent attacks like XSS and clickjacking
Common Authentication Pitfalls
- Plain-text password storage: Always hash passwords
- Weak password hashing algorithms: Use modern algorithms like bcrypt, Argon2, or PBKDF2
- Insecure session management: Use secure, HttpOnly cookies with proper expiration
- Missing logout functionality: Always provide a way for users to terminate their sessions
- Insufficient password recovery mechanisms: Implement secure password reset flows
Summary
In this tutorial, we've covered various approaches to implementing authentication in Python web applications:
- Basic password hashing and verification
- Session-based authentication with Flask-Login
- Token-based authentication with JWT
- OAuth authentication with third-party providers
- Django's built-in authentication system
Each method has its strengths and ideal use cases. Session-based authentication works well for traditional web applications, JWT is excellent for APIs and SPAs, and OAuth is perfect when you want to leverage existing authentication providers.
Remember to always follow security best practices when implementing authentication systems, as they are critical to protecting user data and maintaining trust in your application.
Additional Resources
- Flask-Login Documentation
- PyJWT Documentation
- OWASP Authentication Cheat Sheet
- Django Authentication Documentation
- Flask-OAuthlib for OAuth support
Exercises
- Create a Flask application with user registration and login functionality using SQLAlchemy and Flask-Login.
- Build an API with JWT authentication that has protected and public endpoints.
- Implement a "remember me" feature in your authentication system.
- Add OAuth login with two different providers (e.g., Google and GitHub).
- Create a password reset functionality with secure token generation and verification.
- Implement two-factor authentication using a library like PyOTP.
By working through these exercises, you'll gain practical experience with authentication systems that you can apply to your real-world web development projects.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)