Hands-On tests with 'UserHarbor' and IBM Bob: A Modular Approach to Python User Authentication and Permissions
Introduction
When evaluating open-source libraries for core application infrastructure - such as authentication, session management, and fine-grained Role-Based Access Control (RBAC) - getting hands-on with a complete reference application is invaluable. This is especially true for libraries aiming to be framework-agnostic, promising flexibility but requiring more explicit wiring. Recently, I wanted to explore UserHarbor (github.com/userharbor/userharbor), a lightweight Python user-management library designed without direct coupling to any web framework or database toolkit.
Rather than manually bootstrapping a new project, setting up the SQLite database, and writing boilerplate code to explore every edge of the library, I used IBM Bob, to scaffold and implement an end-to-end reference demonstration integrated with FastAPI, SQLAlchemy for persistence, and SMTP (or a local console fallback) for transactional emails.
The goal was to rapidly test UserHarbor's entire feature lifecycle - registration, email verification, session tokens, optional authentication, RBAC guards, password resets, and account deletion, which I personally find really useful. These capacities could be implemented in many applications and ease the phase of user registration, email validation etc…
This post details the architecture built, highlights the key implementation logic, and illustrates how easily a decoupled core can be integrated into a modern web stack.
UserHabor (from official GitHub repostory)
Image from official project's repository
Project status: UserHarbor is currently in an early stage of development. The API may change frequently. The library is not ready for production use yet.
UserHarbor is a framework-agnostic Python library for user account management.
Its goal is to provide a simple, stable, and framework-independent interface for common user-related operations:
- user registration
- email verification
- login
- session management
- logout from one or all sessions
- password change
- password reset
- account deletion
- role and permission checks
UserHarbor is not a web framework. It does not provide routers, views, or HTTP endpoints. Instead, it exposes a simple domain-level API that can be integrated with FastAPI, Flask, Django, Litestar, CLI applications, or any other environment.
Implementation: System Architecture & Component Design
The application architecture is based on the idea to demonstrate decoupling core business logic(s) from framework adapters, database persistence, and external service gateways, in a universal way.
Component Overview
- UserHarbor Core (userharbor): Manages business logic including user registration, session management, role/permission logic, and password flows.
- Official Adapters:
-
userharbor-fastapi: Handles routing integration and FastAPIDepends()dependency guards. -
userharbor-sqlalchemy: Provides persistence viaSQLAlchemyUserStoremapped toSQLite. -
userharbor-smtp: Handles email dispatching with a local console fallback when SMTP credentials are not present. -
FastAPIWeb Shell (app/): Provides the REST API endpoints and static file serving for a lightweight Single Page Application (SPA).
Implementation Key Highlights
Modular Bootstrapping and Pre-seeding Roles (main.py)
In main.py, IBM Bob established the foundation of the modular design. The application factory bootstraps the SQLite engine, configures UserHarbor with the SQLite store and email factory, pre-seeds initial RBAC permissions, and attaches the authentication routers.
# app/main.py
"""
UserHarbor Demo – FastAPI application entry point.
Stack
─────
* userharbor – core user-management domain logic
* userharbor-sqlalchemy – SQLite-backed UserStore
* userharbor-fastapi – FastAPI router adapter with bearer-token auth
* ConsoleEmailSender / SMTPEmailSender – from app/email.py
Environment variables (copy .env.example → .env):
SECRET_KEY – signing key for session tokens
DATABASE_URL – SQLAlchemy URL (default: sqlite:///./users.db)
SMTP_* – optional SMTP credentials; falls back to console logging
"""
from __future__ import annotations
import os
from pathlib import Path
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from userharbor import UserHarbor
from userharbor_fastapi import UserHarborFastAPI
from userharbor_sqlalchemy import SQLAlchemyUserStore
from app.email import get_email_sender
from app.routes import build_demo_router
FRONTEND_DIR = Path(__file__).parent.parent / "frontend"
load_dotenv()
# ── Database ───────────────────────────────────────────────────────────────
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./users.db")
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine)
# ── UserHarbor setup ───────────────────────────────────────────────────────
store = SQLAlchemyUserStore(SessionLocal)
store.metadata.create_all(engine) # create tables if they don't exist
harbor = UserHarbor(
secret_key=os.getenv("SECRET_KEY", "demo-secret-key-change-in-production"),
store=store,
email_sender=get_email_sender(),
)
# Pre-seed roles and permissions so the demo endpoints work out of the box.
try:
harbor.roles.create("admin")
harbor.roles.create("editor")
harbor.permissions.create("articles.write")
harbor.permissions.create("users.manage")
harbor.roles.grant_permission("admin", "articles.write")
harbor.roles.grant_permission("admin", "users.manage")
harbor.roles.grant_permission("editor", "articles.write")
except Exception:
pass # roles/permissions already exist from a previous run
# ── FastAPI adapter ────────────────────────────────────────────────────────
auth = UserHarborFastAPI(harbor)
# ── Application ────────────────────────────────────────────────────────────
app = FastAPI(
title="UserHarbor Demo",
description=(
"End-to-end demonstration of UserHarbor with FastAPI + SQLAlchemy + SMTP.\n\n"
"Workflow:\n"
"1. `POST /auth/register` – create an account\n"
"2. `POST /auth/verify-email` – verify your email with the token printed to console\n"
"3. `POST /auth/login` – obtain a bearer token\n"
"4. Use the **Authorize** button (🔒) to paste the token\n"
"5. Try the protected `/demo/*` endpoints\n"
),
version="1.0.0",
)
# Built-in auth routes: /auth/register, /auth/login, /auth/me, etc.
app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
# Demo application routes that showcase role/permission guards.
app.include_router(
build_demo_router(auth, harbor),
prefix="/demo",
tags=["Demo – Protected Routes"],
)
# Serve the SPA frontend
app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR



