In multi-tenant Software-as-a-Service (SaaS) web platforms, ensuring that one client cannot access another client's data is a critical security requirement. If you rely on adding manual filters (e.g. WHERE organization_id = ?) to every database query, a developer's oversight can easily lead to data leak vulnerabilities. Google App Engine (GAE) provides built-in multi-tenant isolation via the Namespace Manager. Setting the namespace once scopes all subsequent operations automatically.
By implementing a custom WSGI middleware wrapper, we can intercept incoming requests, validate tenant contexts from authorization headers, and enforce namespace isolation.
1. Structuring Tenant Namespace Middleware
We write a Python WSGI middleware that intercepts requests, decodes authentication tokens, and sets the active namespace before executing views:
# namespace_middleware.py
from google.appengine.api import namespace_manager
import jwt
class TenantNamespaceMiddleware(object):
def __init__(self, app, jwt_secret):
self.app = app
self.jwt_secret = jwt_secret
def __call__(self, environ, start_response):
# Extract JWT token from header
auth_header = environ.get('HTTP_AUTHORIZATION', '')
tenant_id = 'default'
if auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
try:
# Decode and extract tenant namespace context
payload = jwt.decode(token, self.jwt_secret, algorithms=['HS256'])
tenant_id = payload.get('tenant_id', 'default')
except jwt.ExpiredSignatureError:
pass
except jwt.InvalidTokenError:
pass
# Enforce App Engine database namespace scope
namespace_name = f"tenant_{tenant_id}"
namespace_manager.set_namespace(namespace_name)
# Proceed with application request handling
return self.app(environ, start_response)
2. Auto-Scoped Database Queries
Once the namespace is set by the middleware, queries run on the GAE environment are automatically partitioned. For example, executing a query on a CustomerInvoice entity will only search records within the current tenant's database partition, eliminating data leak vectors completely.