Introduction to Authentication and Authorization in CherryPy
Authentication and authorization are two of the most critical security layers in any web application. In CherryPy, a minimalist yet powerful Python web framework, these concepts are implemented with remarkable flexibility and control. This tutorial will guide you through building secure authentication and authorization systems from scratch, explain why they matter, and show you production-ready patterns.
What Is Authentication and Authorization?
Authentication answers the question: "Who are you?" It is the process of verifying the identity of a user, typically through credentials such as a username and password, API keys, or OAuth tokens. Without authentication, your application cannot distinguish between different users or, worse, between a legitimate user and an attacker.
Authorization answers the question: "What are you allowed to do?" Once a user is authenticated, authorization determines which resources, actions, or data that user can access. This is where roles, permissions, and policies come into play. A regular user might view their profile, while an admin can delete accounts. Authorization ensures that even authenticated users cannot exceed their privileges.
Why It Matters
- Data Protection: Prevents unauthorized access to sensitive information such as personal data, financial records, or proprietary business logic.
- Regulatory Compliance: Laws like GDPR, HIPAA, and PCI-DSS mandate strict access controls. Failing to implement them can result in heavy fines.
- User Trust: Users expect their data to be safe. Breaches caused by weak authentication destroy reputation.
- Auditing and Accountability: Authentication provides an audit trail. You know who did what and when, which is essential for debugging and forensics.
- Multi-Tenancy: In applications serving multiple organizations, authorization ensures tenant isolation.
Setting Up a Basic CherryPy Application
Before diving into security, let's create a minimal CherryPy application to serve as our foundation. Install CherryPy if you haven't already:
pip install cherrypy
Create a file named app.py with the following code:
import cherrypy
class Root:
@cherrypy.expose
def index(self):
return "<h1>Welcome to CherryPy</h1><a href='/login'>Login</a>"
@cherrypy.expose
def login(self):
return "<h1>Login Page</h1>"
if __name__ == '__main__':
cherrypy.quickstart(Root())
Run the application:
python app.py
Visit http://localhost:8080 in your browser. You should see the welcome message. This bare-bones app has no security whatsoever — anyone can access any page. Let's change that.
Implementing Authentication in CherryPy
CherryPy does not enforce any particular authentication method. Instead, it provides hooks, tools, and session management that let you build exactly what you need. The most common approach is session-based authentication using a login form.
Enabling Sessions
Sessions allow you to persist user state across requests. Enable the session tool globally or per-application:
import cherrypy
class Root:
@cherrypy.expose
def index(self):
if 'username' in cherrypy.session:
return f"<h1>Hello, {cherrypy.session['username']}!</h1>"
return "<h1>Please log in</h1><a href='/login'>Login</a>"
if __name__ == '__main__':
conf = {
'/': {
'tools.sessions.on': True,
'tools.sessions.timeout': 60 # minutes
}
}
cherrypy.quickstart(Root(), '/', conf)
Now cherrypy.session behaves like a dictionary that persists across requests. The timeout value controls how long a session remains valid without activity.
Login with Credential Verification
Let's implement a real login flow. We'll use a simple in-memory user store for demonstration — in production, you would query a database.
import cherrypy
import hashlib
# Simulated user database — never store plain-text passwords in production!
USERS = {
'alice': {
'password_hash': hashlib.sha256('password123'.encode()).hexdigest(),
'role': 'user'
},
'bob': {
'password_hash': hashlib.sha256('admin456'.encode()).hexdigest(),
'role': 'admin'
}
}
def verify_password(username, password):
if username not in USERS:
return False
expected_hash = USERS[username]['password_hash']
actual_hash = hashlib.sha256(password.encode()).hexdigest()
return actual_hash == expected_hash
class Root:
@cherrypy.expose
def index(self):
if 'username' in cherrypy.session:
username = cherrypy.session['username']
role = USERS.get(username, {}).get('role', 'unknown')
return f"<h1>Welcome, {username} (role: {role})</h1>" \
f"<a href='/logout'>Logout</a>"
return "<h1>Please log in</h1><a href='/login'>Login</a>"
@cherrypy.expose
def login(self, username=None, password=None):
if cherrypy.request.method == 'POST':
if username and password and verify_password(username, password):
cherrypy.session['username'] = username
raise cherrypy.HTTPRedirect('/')
else:
return "<h1>Invalid credentials</h1><a href='/login'>Try again</a>"
# Show login form
return """
<form method='post'>
Username: <input type='text' name='username'><br>
Password: <input type='password' name='password'><br>
<input type='submit' value='Log in'>
</form>
"""
@cherrypy.expose
def logout(self):
cherrypy.session.clear()
raise cherrypy.HTTPRedirect('/')
if __name__ == '__main__':
conf = {
'/': {
'tools.sessions.on': True,
'tools.sessions.timeout': 60
}
}
cherrypy.quickstart(Root(), '/', conf)
Test this application. Log in as alice with password password123 or bob with admin456. The session persists, and the index page shows your username and role. Clicking "Logout" clears the session.