Minimal Password Manager Backend Clone

A lightweight, self-hosted password vault API inspired by Vaultwarden.

Welcome to another official UVF IT beginner build. Today, we are constructing a minimal, working clone of a password manager backend, inspired by the lightweight efficiency of Vaultwarden.

While the real Vaultwarden is a complex Rust application compatible with Bitwarden clients, our goal is to understand the core data flow: secure storage, user authentication, and encrypted item retrieval, using a simplified Python and SQLite stack.

Important Notes

System Architecture
LOCAL DEVELOPMENT ENVIRONMENT REST Requests SQL Queries HTTP Client curl/Postman Flask API Python Backend SQLite DB vault.db
1

Phase 1: Scaffold the Project

Create a new directory for your project and initialize a Python virtual environment to keep dependencies isolated.

Install Flask, the micro web framework we will use to build the API, and sqlite3 is included in the standard library.

mkdir vaultwarden-clone
cd vaultwarden-clone
python3 -m venv venv
source venv/bin/activate
pip install flask
2

Phase 2: Core Data Model

Create a simple SQLite database with two tables: users for authentication and items for storing password data.

In a real Vaultwarden setup, passwords are encrypted client-side. Here, we store them as plain text for simplicity, but we will structure the JSON to mimic the Bitwarden API format.

db_setup.py

import sqlite3

def init_db():
    conn = sqlite3.connect('vault.db')
    c = conn.cursor()
    
    # Create users table
    c.execute('''CREATE TABLE IF NOT EXISTS users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    email TEXT UNIQUE NOT NULL,
                    password TEXT NOT NULL,
                    api_key TEXT UNIQUE NOT NULL
                )''')
    
    # Create ciphers (password items) table
    c.execute('''CREATE TABLE IF NOT EXISTS ciphers (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    user_id INTEGER NOT NULL,
                    name TEXT NOT NULL,
                    username TEXT,
                    password TEXT,
                    FOREIGN KEY(user_id) REFERENCES users(id)
                )''')
    
    # Insert a dummy user for testing
    try:
        c.execute("INSERT INTO users (email, password, api_key) VALUES (?, ?, ?)",
                  ('test@example.com', 'hashed_password_placeholder', 'test-api-key-123'))
        c.execute("INSERT INTO ciphers (user_id, name, username, password) VALUES (?, ?, ?, ?)",
                  (1, 'Test Account', 'admin', 'supersecret'))
    except sqlite3.IntegrityError:
        print("Data already exists.")
        
    conn.commit()
    conn.close()
    print("Database initialized with test data.")

if __name__ == '__main__':
    init_db()
3

Phase 3: Build the API

Create the main application file app.py with Flask routes.

Implement three core endpoints: /register to create a user, /login to authenticate (returning a fake token), and /api/ciphers to list stored passwords for the authenticated user.

app.py

import sqlite3
import json
from flask import Flask, request, jsonify

app = Flask(__name__)

DB_NAME = 'vault.db'

def get_db():
    conn = sqlite3.connect(DB_NAME)
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    email = data.get('email')
    password = data.get('password')
    if not email or not password:
        return jsonify({'error': 'Email and password required'}), 400
    
    db = get_db()
    try:
        db.execute('INSERT INTO users (email, password) VALUES (?, ?)', (email, password))
        db.commit()
        return jsonify({'success': True}), 201
    except sqlite3.IntegrityError:
        return jsonify({'error': 'User already exists'}), 409
    finally:
        db.close()

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    email = data.get('email')
    password = data.get('password')
    
    db = get_db()
    user = db.execute('SELECT * FROM users WHERE email = ? AND password = ?', (email, password)).fetchone()
    db.close()
    
    if user:
        # In a real app, this would be a JWT or session token
        return jsonify({'access_token': f'fake-token-{user["id"]}', 'user_id': user['id']})
    else:
        return jsonify({'error': 'Invalid credentials'}), 401

@app.route('/api/ciphers', methods=['GET'])
def get_ciphers():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({'error': 'Unauthorized'}), 401
    
    # Extract fake user ID from token for demo purposes
    token = auth_header.split(' ')[1]
    try:
        user_id = int(token.split('-')[-1])
    except (IndexError, ValueError):
        return jsonify({'error': 'Invalid token'}), 401
    
    db = get_db()
    ciphers = db.execute('SELECT * FROM items WHERE user_id = ?', (user_id, )).fetchall()
    db.close()
    
    # Convert to list of dicts
    cipher_list = [dict(row) for row in ciphers]
    return jsonify(cipher_list)

if __name__ == '__main__':
    app.run(debug=True, port=5000)
4

Phase 4: Run and Verify

First, run the database setup script to create the tables.

Then, start the Flask server. It will run on http://localhost:5000.

Use curl to test the endpoints: register a user, login to get a token, and then fetch ciphers.

python db_setup.py
python app.py
# Register a user
curl -X POST http://localhost:5000/register -H "Content-Type: application/json" -d '{"email": "test@example.com", "password": "securepass123"}'

# Login to get token
TOKEN=$(curl -s -X POST http://localhost:5000/login -H "Content-Type: application/json" -d '{"email": "test@example.com", "password": "securepass123"}' | python3 -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

# Fetch ciphers (should be empty initially)
curl -X GET http://localhost:5000/api/ciphers -H "Authorization: Bearer $TOKEN"

Verifying the Stack

CheckCommand / Why it matters
Database initializedRun python db_setup.py and verify vault.db file exists in the directory.
Server runningRun python app.py and see 'Running on http://127.0.0.1:5000' in the terminal.
User registration worksCurl the /register endpoint and receive a 201 status with '{"success": true}'.
Cipher retrieval worksCurl the /api/ciphers endpoint with the Authorization header and receive a JSON array containing the test item.

Next Steps

You have built a minimal backend that mimics the core data structure of a password manager. To extend this, try adding a POST endpoint for /api/ciphers to allow creating new items via the API, and implement proper JWT token validation instead of the fake token check.