Bottle-Cork is a lightweight authentication and authorisation library for Python web apps built on the Bottle micro-framework. It gives a small self-hosted application the things you would otherwise re-implement badly: user accounts, role-based permissions, signed session cookies, registration and password-reset flows. This guide explains how Cork works, how it compares to other Python auth options, and how to run an authenticated app safely on your own server.
What Bottle-Cork does at a glance
| Capability | How Cork handles it |
|---|---|
| User accounts | Username, hashed password, e-mail, role, plus arbitrary profile data |
| Authorisation | Named roles with an integer level; decorators enforce a minimum level |
| Sessions | Signed, expiring cookies via Beaker; no server-side session store required for small apps |
| Registration | Pending-registration flow with e-mail confirmation token |
| Password reset | Time-limited, signed reset token delivered by e-mail |
| Storage backend | JSON files, MongoDB or SQLAlchemy |
How Cork works
Cork sits between Bottle's routing and your handlers. Each request that needs protection calls a Cork method that checks the session cookie, resolves the current user, and either allows the handler to run or aborts with a redirect to the login page. Users, roles and pending registrations live in a backend that you choose at start-up. For a single small app the JSON backend is enough; for anything with concurrent writes, move to MongoDB or a SQLAlchemy store.
Roles are ordered by an integer level. An admin at level 100 can do everything an editor at level 50 can, and more. Handlers declare the minimum level they require, so authorisation is expressed once, at the route, instead of being scattered through your business logic.
A minimal authenticated app
from bottle import Bottle, request, redirect
from cork import Cork
from beaker.middleware import SessionMiddleware
aaa = Cork('conf_dir') # backend: users.json, roles.json, ...
app = Bottle()
@app.post('/login')
def login():
aaa.login(request.forms.get('user'),
request.forms.get('pwd'),
success_redirect='/dashboard',
fail_redirect='/login')
@app.route('/dashboard')
def dashboard():
aaa.require(fail_redirect='/login')
return 'Hello %s' % aaa.current_user.username
@app.route('/admin')
def admin():
aaa.require(role='admin', fail_redirect='/login')
return 'Admin area'
wrapped = SessionMiddleware(app, {'session.type': 'cookie',
'session.encrypt_key': 'CHANGE-ME',
'session.validate_key': True})
The two ideas worth taking away even if you never use Cork: authorisation belongs at the route via a single require() call, and session integrity depends entirely on a secret signing key that must be long, random and kept off disk in your source tree.
Need a dedicated server?
Compare prices from top providers. Configure and order in minutes.
Roles and authorisation
Keep the number of roles small and the level gaps wide (for example 0 = private, 30 = user, 60 = editor, 100 = admin). Wide gaps let you insert a new tier later without renumbering everyone. Never check the username to grant power; check the role level. Usernames change, get impersonated in support tickets, and leak into logs.
Password and session security
Cork stores password hashes, not passwords, and signs its session cookies. That is the baseline, not the finish line. Enforce a real minimum password length, rate-limit the login route to blunt credential stuffing, expire sessions on a sensible timeout, and rotate the signing key if it is ever exposed. Serve everything over HTTPS so the session cookie cannot be read on the wire, and set the cookie Secure and HttpOnly.
Bottle-Cork vs other Python authentication options
| Option | Framework | Best for | Trade-off |
|---|---|---|---|
| Bottle-Cork | Bottle / WSGI | Small self-hosted apps that need roles without a database | Minimal ecosystem; you wire e-mail and rate-limiting yourself |
| Flask-Login | Flask | Session management in Flask apps | Only handles sessions; roles and registration are your job |
| Authlib | Any | OAuth2 / OpenID Connect providers and clients | Heavier; overkill for a single internal app |
| Django auth | Django | Full apps that already use Django's ORM | You buy into the whole framework |
| Authelia / Keycloak | Standalone SSO | Protecting several services behind one login | A separate service to run and secure itself |
If you already run more than a couple of self-hosted services, a standalone identity provider such as Keycloak is often the better long-term answer — see our guide on installing Keycloak on a server for SSO and IAM. For a single Bottle app, Cork stays out of your way.
Need a dedicated server?
Compare prices from top providers. Configure and order in minutes.
Hardening the server behind your login
Authentication is only as strong as the machine it runs on. A perfect login page does nothing if the database port is open to the internet or an old admin panel answers on port 8080. Treat the host itself as part of the auth boundary:
- Firewall by default-deny. Expose only 443 (and SSH on a key-only, ideally non-default configuration). Tools in the tradition of Firelet, and today
nftablesorufw, let you describe the allowed traffic explicitly and audit it as a ruleset rather than a pile of ad-hoc commands. - Fail2ban on the login and SSH logs to ban brute-force sources.
- TLS everywhere with automatic certificate renewal; redirect all HTTP to HTTPS.
- A reverse proxy (nginx or Caddy) in front of the Python process, so the app never faces the internet directly and you get one place to add headers and rate limits.
- Least privilege: run the app as a non-root user, and keep the auth backend file readable only by that user.
The first thirty minutes on a new box set the tone for everything after — our checklist for securing a dedicated server right after delivery covers the firewall, SSH and update steps in order.
Choosing a server to self-host an authenticated app
Once real users log in, you want predictable CPU and memory, a fixed IP, and full control of the firewall — none of which you fully get on shared hosting. A private virtual server works for a light app; a dedicated server is the right call once you store real user data, run a database, or need guaranteed resources with no noisy neighbours. If cost is the constraint, a budget dedicated server still gives you the isolation and firewall control that authentication depends on.
Bottle-Cork itself is small, and that is the point: the security work that matters is not the library, it is the discipline around it — hashed credentials, role-based authorisation, signed sessions, and a hardened host underneath. Get those right and even a hundred-line Bottle app is safe to put on the open internet.