DEV Community

Cover image for I Built a Free Auth0 Alternative That Gives You 20+ Routes in One Line of Code
Dhruv Agnihotri
Dhruv Agnihotri

Posted on

I Built a Free Auth0 Alternative That Gives You 20+ Routes in One Line of Code

TL;DR: Open-source auth that costs $0/year (vs $2,880+ for Auth0), sets up in 2 minutes, gives you 20+ production routes in one line, and includes an industry-first "smart cookie fallback". Both packages work independently or together. I'm using it in production for my own projects.

πŸ”— Links: HeadlessKit Hub β€’ React (NPM) β€’ Flask (PyPI) β€’ React GitHub β€’ Flask GitHub


Table of Contents


The $2,880+/Year Problem

You're building a new app. You need authentication. Your options?

Solution Cost/Year Setup Time The Catch
Auth0 $2,880+ 20 min Vendor lock-in, expensive (Professional: $240/mo)
Clerk $300+ 15 min Vendor lock-in, expensive with add-ons ($100/mo each)
NextAuth Free 30 min Manual backend, DIY security
Supabase Free tier 15 min Limited, vendor lock-in
Build it yourself Free 2-3 weeks JWT rotation, OAuth, MFA, password reset...

I faced this exact problem for my side projects (PDFCourt.com and ShuffleTurn.com). As an indie developer, I couldn't justify $240-300/month for basic auth. But building from scratch meant weeks of work handling JWT rotation, OAuth, token refresh, password reset flows, email verification...

So I built this library. And now I'm open-sourcing it.

What This Is

  • βœ… MIT licensed - Use it however you want
  • βœ… Self-hosted - Your data, your server
  • βœ… No vendor lock-in - Standard JWT, REST APIs
  • βœ… Battle-tested - Running my own production apps
  • βœ… Actively maintained - I use this daily, so I keep it updated

What This Isn't

  • ❌ Not trying to replace Auth0/Clerk for enterprises
  • ❌ Not a VC-backed startup (just an indie dev)
  • ❌ Not claiming thousands of users (yet!)
  • ❌ Not promising 24/7 support

This is a tool I built for myself and am sharing with the community. If it helps you, awesome!


Meet HeadlessKit Authentication

Two packages. Three ways to use them. Zero vendor lock-in.

πŸ“¦ The Packages

Frontend: @headlesskits/react-headless-auth

npm install @headlesskits/react-headless-auth
Enter fullscreen mode Exit fullscreen mode

Backend: flask-headless-auth

pip install flask-headless-auth
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ Three Architectures, One System

1️⃣ Full Stack (React + Flask)

Use case: New projects, rapid prototyping, maximum convenience

# Backend: One line
auth = AuthSvc(app)
Enter fullscreen mode Exit fullscreen mode
// Frontend: One component
<AuthProvider config={{ apiBaseUrl: 'http://localhost:5000' }}>
  <App />
</AuthProvider>
Enter fullscreen mode Exit fullscreen mode

Result: Complete auth system in 2 minutes. 20+ routes. Zero config.


2️⃣ Backend Only (Flask β†’ Any Frontend)

Use case: You have Vue/Angular/Svelte/Mobile app, need a backend

auth = AuthSvc(app)  # 20+ REST endpoints ready
Enter fullscreen mode Exit fullscreen mode

Works with:

  • Vue, Angular, Svelte, Solid.js
  • React Native, Flutter, iOS, Android
  • Desktop apps (Electron, Tauri)
  • ANY HTTP client

What you get: Production-ready REST API with JWT auth, OAuth, MFA, password reset, email verification. Just point your frontend to the endpoints.


3️⃣ Frontend Only (React β†’ Any Backend)

Use case: You have Express/Django/FastAPI backend, need a frontend

<AuthProvider config={{ apiBaseUrl: 'https://your-api.com' }}>
  <App />
</AuthProvider>
Enter fullscreen mode Exit fullscreen mode

Works with: Express, Django, FastAPI, .NET, Rails, Go, Rust...

What you need: Implement 5 simple endpoints:

  • POST /api/auth/login β†’ { user, access_token, refresh_token }
  • POST /api/auth/signup β†’ { user, access_token, refresh_token }
  • GET /api/auth/user/@me β†’ { user }
  • POST /api/auth/logout β†’ { message }
  • POST /api/auth/token/refresh β†’ { access_token, refresh_token }

Bonus: The React package uses a framework-agnostic core. Import AuthClient directly for Vue, Svelte, or vanilla JS.


Architecture Overview

Understanding how HeadlessKit works under the hood helps you make informed decisions and extend it effectively.

Core Design Principles

1. Framework-Agnostic Core

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         React Layer                     β”‚
β”‚  AuthProvider, useAuth, hooks           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Framework-Agnostic Core            β”‚
β”‚  AuthClient + TokenStorage              β”‚
β”‚  (Works with ANY framework)             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Backend API                     β”‚
β”‚  Flask/Express/Django/FastAPI           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

The React components are a thin wrapper around AuthClient, which handles all auth logic. This means:

  • Easy to port to Vue, Svelte, Angular
  • Can use directly in Node.js or React Native
  • Business logic stays framework-independent

Authentication Flow

Login/Signup Flow:

1. User submits credentials
   └─> AuthClient.login(email, password)
       └─> POST /api/auth/login
           └─> Backend validates credentials
               └─> Returns { user, access_token, refresh_token }
                   └─> TokenStorage detects cookie support
                       β”œβ”€> βœ… Cookies work β†’ Store in httpOnly cookies
                       └─> ❌ Cookies blocked β†’ Store in localStorage
                           └─> Schedule automatic token refresh
                               └─> User object stored in React Context
Enter fullscreen mode Exit fullscreen mode

Token Refresh Flow:

1. AuthClient decodes JWT access_token
   └─> Reads expiry time (exp claim)
       └─> Schedules refresh 5 minutes before expiration
           └─> When time arrives:
               └─> POST /api/auth/token/refresh
                   └─> Sends refresh_token
                       └─> Backend validates & rotates tokens
                           └─> Returns new access_token + refresh_token
                               └─> Tokens updated silently (user stays logged in)
Enter fullscreen mode Exit fullscreen mode

Key insight: No fixed intervals. The system reads your JWT's actual expiry and refreshes precisely when needed.


Backend Architecture (Flask)

Component Hierarchy:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            AuthSvc                      β”‚
β”‚  (Main entry point - one line setup)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β”œβ”€> UserManager (user CRUD, validation)
               β”œβ”€> TokenManager (JWT, refresh, blacklist)
               β”œβ”€> AuthManager (login, signup, password)
               β”œβ”€> OAuthManager (Google, Microsoft)
               └─> RBACManager (roles, permissions)

Each manager is independent and composable
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • Want just OAuth? Import OAuthManager only
  • Custom user logic? Extend UserManager
  • Different database? Swap UserRepository

All managers follow a single-responsibility design, making the codebase maintainable and extensible.


Token Storage Strategy

Frontend Storage Decision Tree:

// On first login:
1. Try to set a test cookie
2. Try to read it back
3. If successful β†’ use httpOnly cookies (secure)
4. If blocked β†’ use localStorage (compatible)
5. Remember choice for session
Enter fullscreen mode Exit fullscreen mode

Backend Token Management:

# Three-tier token system:
1. Access Token (short-lived, 15 min)
   └─> Used for API requests

2. Refresh Token (long-lived, 30 days)
   └─> Used to get new access tokens

3. Token Blacklist (Redis/DB)
   └─> Invalidated tokens on logout
Enter fullscreen mode Exit fullscreen mode

This prevents the common problem where users with cookie blockers can't use your app, while maintaining maximum security for those who allow cookies.


State Management (React)

Context Architecture:

<AuthProvider>           // Top-level wrapper
  └─> AuthContext        // React Context
      β”œβ”€> AuthClient     // Core logic
      β”œβ”€> TokenStorage   // Storage abstraction
      └─> State:
          β”œβ”€> user (object | null)
          β”œβ”€> isAuthenticated (boolean)
          β”œβ”€> isLoading (boolean)
          └─> error (object | null)
Enter fullscreen mode Exit fullscreen mode

Why Context + Core Client?

  • Context handles React-specific state updates
  • AuthClient handles framework-agnostic logic
  • Easy to test each layer independently
  • Can use AuthClient without React

Database Schema (Flask)

Minimal required schema:

users
β”œβ”€ id (Primary Key)
β”œβ”€ email (Unique, Indexed)
β”œβ”€ password_hash
β”œβ”€ email_confirmed (Boolean)
β”œβ”€ mfa_enabled (Boolean)
β”œβ”€ mfa_secret
β”œβ”€ created_at
└─ updated_at

token_blacklist
β”œβ”€ jti (JWT ID)
└─ expires_at
Enter fullscreen mode Exit fullscreen mode

Extensible: Add any fields you need. The library validates that required fields exist at startup.


Security Layers

Defense in Depth:

1. Transport Layer
   └─> HTTPS, Secure cookies, SameSite attribute

2. Storage Layer
   └─> httpOnly cookies (XSS-proof) or encrypted localStorage

3. Token Layer
   └─> JWT signing, expiry, rotation, blacklisting

4. Application Layer
   └─> bcrypt hashing, rate limiting, input validation

5. Database Layer
   └─> Parameterized queries (SQLAlchemy ORM)
Enter fullscreen mode Exit fullscreen mode

No single point of failure. Multiple security mechanisms protect your auth system.


Extensibility Philosophy

The architecture is designed around progressive enhancement:

  1. Start simple - Default configuration works out of the box
  2. Extend when needed - Add hooks, custom models, or swap components
  3. Never locked in - Standard REST APIs and JWT tokens mean you can migrate away anytime

Every component has extension points: lifecycle hooks in the frontend, custom models in the backend, and swappable services (email, storage, database) throughout. The system uses composition over inheritance, making it easy to replace individual pieces without touching others.

See the Extensibility section below for detailed code examples and configuration options.


2-Minute Full Stack Setup

Let's see the full-stack approach in action.

Backend (10 lines = 20+ routes)

from flask import Flask
from flask_headless_auth import AuthSvc

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['JWT_SECRET_KEY'] = 'jwt-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

# THIS IS THE MAGIC LINE πŸͺ„
auth = AuthSvc(app)

if __name__ == '__main__':
    app.run()
Enter fullscreen mode Exit fullscreen mode

Frontend (Complete auth in 3 lines)

import { AuthProvider, useAuth } from '@headlesskits/react-headless-auth';

// 1. Wrap your app
<AuthProvider config={{ apiBaseUrl: 'http://localhost:5000' }}>
  <App />
</AuthProvider>

// 2. Use anywhere in your components
function MyComponent() {
  const { user, login, logout, isAuthenticated } = useAuth();

  return (
    <div>
      {isAuthenticated ? (
        <>
          <h1>Welcome {user.email}!</h1>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={() => login(email, password)}>Login</button>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

That's it! Full authentication in less than 50 lines total.


⚑ What You Get Instantly

Backend: 20+ Production-Ready Routes

One line of code (auth = AuthSvc(app)) gives you:

Core Authentication:

  • βœ… POST /api/auth/signup - User registration with validation
  • βœ… POST /api/auth/login - JWT authentication
  • βœ… POST /api/auth/logout - Token blacklisting
  • βœ… GET /api/auth/user/@me - Get current user
  • βœ… POST /api/auth/token/refresh - Automatic token refresh

OAuth Providers:

  • βœ… GET /api/auth/login/google - Google OAuth
  • βœ… GET /api/auth/login/microsoft - Microsoft OAuth
  • βœ… GET /api/auth/callback/google - OAuth callback handling
  • βœ… GET /api/auth/callback/microsoft - OAuth callback handling

Password Management:

  • βœ… POST /api/auth/password/update - Change password
  • βœ… POST /api/auth/request-password-reset - Initiate reset flow
  • βœ… POST /api/auth/reset-password/<token> - Complete password reset
  • βœ… POST /api/auth/validate-reset-token - Verify reset token

Email & Verification:

  • βœ… GET /api/auth/confirm/<token> - Email verification
  • βœ… POST /api/auth/resend-confirmation - Resend verification email

Multi-Factor Authentication:

  • βœ… POST /api/auth/mfa/enable - Enable 2FA
  • βœ… POST /api/auth/mfa/verify - Verify 2FA code
  • βœ… POST /api/auth/mfa/disable - Disable 2FA

User Management:

  • βœ… GET /api/auth/users - List users (admin)
  • βœ… DELETE /api/auth/user/<id> - Delete user (admin)
  • βœ… PUT /api/auth/user/<id> - Update user

πŸ“– See full API documentation


Frontend: Complete Auth State Management

const {
  // User data
  user,                    // Current user object
  isAuthenticated,         // Boolean auth state
  isLoading,              // Loading state

  // Actions
  login,                  // (email, password) => Promise
  signup,                 // (email, password) => Promise
  logout,                 // () => Promise
  updatePassword,         // (old, new) => Promise

  // Token management
  refreshToken,           // Manual refresh (auto-handled)

  // Error handling
  error                   // Last error object
} = useAuth();
Enter fullscreen mode Exit fullscreen mode

Additional hooks:

// Just the user data
const user = useUser();

// Just the session state
const { isAuthenticated, isLoading } = useSession();
Enter fullscreen mode Exit fullscreen mode

Security: Production-Ready, Zero Config

All of this comes built-in, no configuration needed:

  • βœ… httpOnly cookies - XSS-proof token storage
  • βœ… bcrypt hashing - Password hashing (cost factor 12)
  • βœ… JWT rotation - Automatic token refresh & rotation
  • βœ… Token blacklisting - Invalidate tokens on logout
  • βœ… CSRF protection - SameSite cookies
  • βœ… Rate limiting - Prevent brute force attacks
  • βœ… Input validation - SQL injection & XSS prevention
  • βœ… Secure password reset - Time-limited tokens

Secure by default. No security expertise required.


πŸ† Innovation #1: Smart Cookie Fallback

Here's a feature I'm particularly proud of.

The Problem Other Libraries Have

Most auth libraries force you to choose:

  • Cookies (most secure, httpOnly, XSS-proof) OR
  • localStorage (works when cookies blocked, but vulnerable to XSS)

Result: 1-2% of users with strict privacy settings get "Please enable cookies" errors and can't use your app.

My Solution: Intelligent Fallback

HeadlessKit uses BOTH automatically:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         User logs in                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Test cookie support  β”‚
    β”‚ (automatic, silent)  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                β”‚
    βœ… YES            ❌ NO
       β”‚                β”‚
       β–Ό                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ httpOnly     β”‚  β”‚ localStorage   β”‚
β”‚ Cookies      β”‚  β”‚ Fallback       β”‚
β”‚              β”‚  β”‚                β”‚
β”‚ 99% of users β”‚  β”‚ 1% of users    β”‚
β”‚ XSS-proof βœ… β”‚  β”‚ Still works ⚠️ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

The Result

  • βœ… 99% of users get maximum security (httpOnly cookies)
  • βœ… 1% with blocked cookies still have a working app (localStorage)
  • βœ… Zero configuration needed
  • βœ… Automatic detection - happens silently on first login
  • βœ… No error screens - no "please enable cookies" messages
  • βœ… Graceful degradation - best security available for each user

Why This Matters

Most libraries either:

  1. Use cookies only (breaks for 1-2% of users with strict privacy settings)
  2. Use localStorage only (less secure for everyone)
  3. Make you choose (adds complexity)

This approach: Best security for each user, automatically.


🧠 Innovation #2: JWT-Aware Token Refresh

Most libraries refresh tokens on a fixed interval (e.g., every 50 minutes). This is inefficient and can cause issues.

The Smart Approach

HeadlessKit decodes your JWT, reads the actual expiry time, and schedules refresh exactly when needed:

// Inside AuthClient.ts
private scheduleTokenRefresh(token: string): void {
  const payload = this.decodeJWT(token);

  if (payload?.exp) {
    // JWT has expiry - refresh 5 min before expiration
    const expiryTime = payload.exp * 1000;
    const refreshTime = expiryTime - (5 * 60 * 1000);
    const delay = Math.max(0, refreshTime - Date.now());

    this.refreshTimeoutId = setTimeout(async () => {
      await this.refreshToken();
    }, delay);
  }
}
Enter fullscreen mode Exit fullscreen mode

Benefits

  • βœ… Precise timing - refreshes exactly when needed
  • βœ… No wasted requests - doesn't refresh too early
  • βœ… Seamless UX - users stay logged in without interruption
  • βœ… Respects your backend - uses the expiry time YOU set
  • βœ… Works with any JWT - reads standard exp claim

πŸ“Š The Comparison

Feature HeadlessKit NextAuth Clerk Auth0 Supabase
Setup Time ⚑ 2 minutes 30 min 15 min 20 min 15 min
Monthly Cost βœ… $0 Free $25-325+ $240+ Free tier limited
Annual Cost βœ… $0 Free $300-3,900+ $2,880+ Scales with usage
Smart Cookie Fallback βœ… Yes ❌ ❌ ❌ ❌
Works Independently βœ… Mix & Match ⚠️ Backend DIY ❌ ❌ ⚠️ Complex
One-Line Backend βœ… AuthSvc(app) ❌ DIY βœ… βœ… βœ…
Custom User Models βœ… With validation βœ… ❌ ❌ ⚠️ Limited
Self-Hosted βœ… Full control βœ… ❌ ❌ ⚠️ Complex
Bundle Size βœ… 15KB ❌ Heavy βœ… Small βœ… Small ⚠️ Medium
JWT-Aware Refresh βœ… Smart ⚠️ Manual βœ… βœ… βœ…
Vendor Lock-in βœ… None βœ… None ❌ High ❌ High ⚠️ Medium
OAuth Providers βœ… Google, MS βœ… Many βœ… Many βœ… Many βœ… Many
MFA/2FA βœ… Built-in ⚠️ Manual βœ… βœ… βœ…
Email Verification βœ… Built-in ⚠️ Manual βœ… βœ… βœ…
RBAC βœ… Built-in ⚠️ Manual βœ… βœ… βœ…

πŸ’Ό How I'm Using This in Production

I built this library because I needed it for my own projects. Here's how I'm using it:

PDFCourt.com - Legal Document Processing

  • Custom user model with subscription tiers
  • Quota tracking per user
  • Self-hosted for data compliance
class User(db.Model, UserMixin):
    subscription_tier = db.Column(db.String(50), default='free')
    documents_processed = db.Column(db.Integer, default=0)
    monthly_limit = db.Column(db.Integer, default=10)

auth = AuthSvc(app, user_model=User)

@app.route('/api/auth/check-quota')
@jwt_required()
def check_quota():
    user = User.query.get(get_jwt_identity())
    remaining = user.monthly_limit - user.documents_processed
    return {'remaining': remaining, 'can_process': remaining > 0}
Enter fullscreen mode Exit fullscreen mode

ShuffleTurn.com - Gaming Platform

  • OAuth integration (Google, Microsoft)
  • Analytics integration via lifecycle hooks
  • Custom user fields for gaming stats
<AuthProvider
  config={{ apiBaseUrl: 'https://api.shuffleturn.com' }}
  hooks={{
    afterLogin: ({ user }) => {
      posthog.identify(user.id, {
        username: user.username,
        level: user.level
      });
    },
    transformUser: ({ user }) => ({
      ...user,
      rankTitle: getRankTitle(user.level),
      isPro: user.level >= 50
    })
  }}
>
  <App />
</AuthProvider>
Enter fullscreen mode Exit fullscreen mode

Why I'm confident sharing this: I'm using this exact code in production. If it works for my projects, it can work for yours.


🎨 When You Need More: Extensibility

Start simple. Extend when needed. Here's how:

1. Lifecycle Hooks (Frontend)

Inject custom logic at any point in the auth flow:

<AuthProvider
  config={{ apiBaseUrl: 'http://localhost:5000' }}
  hooks={{
    // After successful login
    afterLogin: ({ user }) => {
      analytics.identify(user.id);
      posthog.track('User Logged In');
    },

    // On login error
    onLoginError: ({ error }) => {
      Sentry.captureException(error);
      toast.error(error.message);
    },

    // After successful signup
    afterSignup: ({ user }) => {
      analytics.track('User Signed Up');
      // Trigger onboarding flow
    },

    // Transform user data
    transformUser: ({ user }) => ({
      ...user,
      fullName: `${user.first_name} ${user.last_name}`,
      isAdmin: user.roles?.includes('admin')
    }),

    // After token refresh
    afterTokenRefresh: ({ access_token }) => {
      console.log('Token refreshed silently');
    },

    // After logout
    afterLogout: () => {
      analytics.reset();
      // Clear app state
    },

    // Global auth error handler
    onAuthError: ({ error }) => {
      if (error.status === 401) {
        // Redirect to login
      }
    }
  }}
>
  <App />
</AuthProvider>
Enter fullscreen mode Exit fullscreen mode

Available hooks:

  • afterLogin - Post-login logic
  • afterSignup - Post-signup logic
  • afterLogout - Post-logout cleanup
  • onLoginError - Login error handling
  • onSignupError - Signup error handling
  • afterTokenRefresh - Token refresh callback
  • transformUser - Transform user data
  • onAuthError - Global error handler

Perfect for:

  • Analytics (PostHog, Mixpanel, Amplitude)
  • Error tracking (Sentry, LogRocket)
  • Onboarding flows
  • Data transformation
  • Custom redirects

2. Custom User Models (Backend)

Add any fields you need to the user model:

from flask_headless_auth import AuthSvc, UserMixin

class User(db.Model, UserMixin):
    __tablename__ = 'users'

    # Add ANY custom fields
    company = db.Column(db.String(200))
    subscription_tier = db.Column(db.String(50), default='free')
    monthly_quota = db.Column(db.Integer, default=100)
    username = db.Column(db.String(50), unique=True)
    level = db.Column(db.Integer, default=1)
    avatar_url = db.Column(db.String(500))
    bio = db.Column(db.Text)
    preferences = db.Column(db.JSON)

auth = AuthSvc(app, user_model=User)
Enter fullscreen mode Exit fullscreen mode

Bonus: Model Validation

We validate your model at startup. Missing required fields? You get a clear error with the exact fix:

❌ USER MODEL VALIDATION FAILED

Missing required field: mfa_enabled
Type: Boolean
Default: False

SQL Fix:
ALTER TABLE users ADD COLUMN mfa_enabled BOOLEAN DEFAULT FALSE;
Enter fullscreen mode Exit fullscreen mode

No cryptic runtime errors in production!


3. Custom JWT Claims

Add custom data to JWT tokens:

@auth.additional_claims_loader
def add_claims_to_jwt(identity):
    user = User.query.get(identity)
    return {
        'role': user.role,
        'subscription': user.subscription_tier,
        'company_id': user.company_id,
        'permissions': user.get_permissions()
    }
Enter fullscreen mode Exit fullscreen mode

Access in protected routes:

from flask_jwt_extended import get_jwt

@app.route('/api/admin/users')
@jwt_required()
def admin_users():
    claims = get_jwt()

    if claims.get('role') != 'admin':
        return {'error': 'Unauthorized'}, 403

    # Admin logic here
Enter fullscreen mode Exit fullscreen mode

4. Custom Routes & Endpoints

Add your own protected endpoints:

from flask_jwt_extended import jwt_required, get_jwt_identity

auth = AuthSvc(app)

@app.route('/api/auth/check-quota')
@jwt_required()
def check_quota():
    user_id = get_jwt_identity()
    user = User.query.get(user_id)

    if user.subscription_tier == 'free' and user.usage > 100:
        return {'error': 'Upgrade required'}, 403

    return {'remaining': user.monthly_quota - user.usage}

@app.route('/api/auth/upgrade')
@jwt_required()
def upgrade_subscription():
    user_id = get_jwt_identity()
    user = User.query.get(user_id)

    user.subscription_tier = 'pro'
    user.monthly_quota = 1000
    db.session.commit()

    return {'message': 'Upgraded successfully'}
Enter fullscreen mode Exit fullscreen mode

5. Framework-Agnostic Core (No React Required)

Use the core AuthClient with any framework:

import { AuthClient, TokenStorage } from '@headlesskits/react-headless-auth/core';

// Initialize
const storage = new TokenStorage('cookie-first');
const authClient = new AuthClient(
  { apiBaseUrl: 'https://api.example.com' },
  storage
);

// Use anywhere
await authClient.login(email, password);
const user = await authClient.getUser();
await authClient.logout();
Enter fullscreen mode Exit fullscreen mode

Works in:

  • βœ… Vue 3, Svelte, Angular, Solid.js
  • βœ… React Native (with AsyncStorage adapter)
  • βœ… Electron (with secure storage)
  • βœ… Vanilla JavaScript
  • βœ… Node.js (server-side)

Example: Vue 3 Composition API

import { ref, onMounted } from 'vue';
import { AuthClient, TokenStorage } from '@headlesskits/react-headless-auth/core';

export function useAuth() {
  const user = ref(null);
  const isAuthenticated = ref(false);

  const storage = new TokenStorage('cookie-first');
  const authClient = new AuthClient({ apiBaseUrl: '...' }, storage);

  const login = async (email: string, password: string) => {
    const result = await authClient.login(email, password);
    user.value = result.user;
    isAuthenticated.value = true;
  };

  onMounted(async () => {
    try {
      user.value = await authClient.getUser();
      isAuthenticated.value = true;
    } catch {
      isAuthenticated.value = false;
    }
  });

  return { user, isAuthenticated, login };
}
Enter fullscreen mode Exit fullscreen mode

6. Email Service Configuration

Built-in support for Gmail and Brevo (SendInBlue):

# Gmail
app.config['EMAIL_SERVICE'] = 'gmail'
app.config['GMAIL_ADDRESS'] = 'your-email@gmail.com'
app.config['GMAIL_APP_PASSWORD'] = 'your-app-password'

# OR Brevo
app.config['EMAIL_SERVICE'] = 'brevo'
app.config['BREVO_API_KEY'] = 'your-api-key'
app.config['BREVO_SENDER_EMAIL'] = 'noreply@yourdomain.com'
app.config['BREVO_SENDER_NAME'] = 'Your App'

auth = AuthSvc(app)
Enter fullscreen mode Exit fullscreen mode

Or bring your own email service:

from flask_headless_auth.interfaces import EmailServiceInterface

class CustomEmailService(EmailServiceInterface):
    def send_email(self, to: str, subject: str, body: str):
        # Your email logic here
        pass

auth = AuthSvc(app, email_service=CustomEmailService())
Enter fullscreen mode Exit fullscreen mode

7. OAuth Provider Configuration

Add Google and Microsoft OAuth:

# Google OAuth
app.config['GOOGLE_CLIENT_ID'] = 'your-google-client-id'
app.config['GOOGLE_CLIENT_SECRET'] = 'your-google-client-secret'
app.config['GOOGLE_REDIRECT_URI'] = 'http://localhost:5000/api/auth/callback/google'

# Microsoft OAuth
app.config['MICROSOFT_CLIENT_ID'] = 'your-microsoft-client-id'
app.config['MICROSOFT_CLIENT_SECRET'] = 'your-microsoft-client-secret'
app.config['MICROSOFT_REDIRECT_URI'] = 'http://localhost:5000/api/auth/callback/microsoft'

auth = AuthSvc(app)
Enter fullscreen mode Exit fullscreen mode

Frontend usage:

function LoginPage() {
  const handleGoogleLogin = () => {
    window.location.href = 'http://localhost:5000/api/auth/login/google';
  };

  return (
    <button onClick={handleGoogleLogin}>
      Sign in with Google
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Security Deep Dive

What's Included Out of the Box

Password Security:

  • bcrypt hashing with cost factor 12
  • Minimum password requirements (8+ chars, uppercase, lowercase, number)
  • Password strength validation
  • Secure password reset with time-limited tokens

Token Security:

  • JWT with RS256 or HS256 signing
  • Automatic token rotation
  • Token blacklisting on logout
  • Refresh token rotation
  • httpOnly cookies (XSS-proof)
  • SameSite cookie attribute (CSRF protection)

API Security:

  • Rate limiting on auth endpoints (10 requests/minute)
  • Input validation and sanitization
  • SQL injection prevention (SQLAlchemy ORM)
  • CORS configuration support
  • Request size limits

Session Security:

  • Automatic session timeout
  • Concurrent session management
  • Device tracking (optional)
  • IP-based validation (optional)

Security Best Practices

The library follows OWASP guidelines:

  • βœ… A01: Broken Access Control - JWT-based auth with role validation
  • βœ… A02: Cryptographic Failures - bcrypt, secure token generation
  • βœ… A03: Injection - Parameterized queries, input validation
  • βœ… A04: Insecure Design - Secure by default configuration
  • βœ… A05: Security Misconfiguration - Sensible defaults
  • βœ… A07: Identification and Authentication Failures - MFA support, secure password reset

πŸš€ Get Started in 2 Minutes

Option 1: Full Stack (React + Flask)

Step 1: Install packages

# Backend
pip install flask-headless-auth

# Frontend
npm install @headlesskits/react-headless-auth
Enter fullscreen mode Exit fullscreen mode

Step 2: Backend setup

from flask import Flask
from flask_headless_auth import AuthSvc

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['JWT_SECRET_KEY'] = 'jwt-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

auth = AuthSvc(app)

if __name__ == '__main__':
    app.run()
Enter fullscreen mode Exit fullscreen mode

Step 3: Frontend setup

import { AuthProvider, useAuth } from '@headlesskits/react-headless-auth';

function App() {
  return (
    <AuthProvider config={{ apiBaseUrl: 'http://localhost:5000' }}>
      <YourApp />
    </AuthProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Done! You have full authentication.


Option 2: Backend Only

pip install flask-headless-auth
Enter fullscreen mode Exit fullscreen mode
from flask import Flask
from flask_headless_auth import AuthSvc

app = Flask(__name__)
# ... config ...

auth = AuthSvc(app)  # 20+ REST endpoints ready
Enter fullscreen mode Exit fullscreen mode

Point your frontend (Vue, Angular, mobile app) to the API endpoints.


Option 3: Frontend Only

npm install @headlesskits/react-headless-auth
Enter fullscreen mode Exit fullscreen mode
<AuthProvider config={{ apiBaseUrl: 'https://your-backend.com' }}>
  <App />
</AuthProvider>
Enter fullscreen mode Exit fullscreen mode

Implement 5 endpoints on your backend (Express, Django, etc.).


πŸ“š Documentation & Resources


πŸ—ΊοΈ Roadmap

Coming soon:

  • Vue.js & Svelte SDKs
  • Magic links (passwordless auth)
  • WebAuthn/Passkeys support
  • Express.js & FastAPI backends
  • React Native & Flutter SDKs
  • GitHub & Apple OAuth
  • Admin dashboard UI

Want to contribute? Check out CONTRIBUTING.md


🌟 Join the Community

If this helps you, please:

@headlesskits/react-headless-auth

npm version npm downloads License: MIT TypeScript Bundle Size

πŸš€ Production-ready React authentication in 2 minutes. Smart cookie fallback, automatic token refresh, zero dependencies. The simplest way to add enterprise-grade auth to your React app.

npm install @headlesskits/react-headless-auth
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Why Choose This?

The Problem: Authentication is hard. Auth0 costs $300/month. Building it yourself takes weeks. Most libraries force you to choose between security (cookies) OR compatibility (localStorage).

Our Solution: Best of both worlds. Maximum security for 99% of users (httpOnly cookies), automatic fallback for the 1% with blocked cookies (localStorage). Plus a complete backend SDK so you don't spend weeks building auth routes.

Feature react-headless-auth NextAuth Clerk Auth0 Supabase Auth
Setup Time ⚑ 2 minutes 30 min 15 min 20 min 15 min
Monthly Cost βœ… $0 Free $300 $240 Free tier limited
Smart Cookie Fallback βœ… Industry First ❌ ❌ ❌ ❌
Zero Dependencies βœ… (~15KB) ❌ (heavy) βœ… βœ… ⚠️ (medium)
Backend Included βœ… flask-headless-auth ⚠️
…

Flask-Headless-Auth

PyPI version Python 3.8+ Downloads License: MIT Code style: black

πŸ” Production-ready Flask authentication in one line. Get 20+ auth routes instantly. JWT, OAuth, MFA, RBAC built-in. Works with React, Next.js, Vue, any frontend. The free, self-hosted alternative to Auth0/Clerk ($3,600/year saved).


πŸ’‘ What You Get

In one line of code (AuthSvc(app)), you get a complete authentication system that would take weeks to build:

auth = AuthSvc(app)  # That's it! πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

Instantly Available:

  • βœ… 20+ Production Routes - Login, signup, OAuth, password reset, MFA, profile management
  • βœ… JWT + httpOnly Cookies - Maximum security with automatic fallback
  • βœ… OAuth Ready - Google & Microsoft sign-in (GitHub, Apple coming soon)
  • βœ… MFA/2FA - Multi-factor authentication built-in
  • βœ… RBAC - Role-based access control
  • βœ… Email Services - Verification & password reset emails
  • βœ… Rate Limiting - Brute force protection
  • βœ… Token Blacklisting - Secure logout
  • βœ… Security Headers - CSRF, XSS, CORS protection
  • βœ… Custom User…

Your support helps other developers discover this project!


🎯 Final Thoughts

Authentication doesn't have to be hard, expensive, or lock you into a vendor. With HeadlessKit, you get:

  • βœ… Production-ready auth in 2 minutes
  • βœ… Smart cookie fallback for maximum compatibility
  • βœ… 20+ routes from one line of code
  • βœ… Complete control over your data
  • βœ… Zero recurring costs

Built by an indie dev for indie devs. No venture capital. No pricing tiers. No vendor lock-in. Just great open-source software.


Questions? Comments? Want to contribute?

Drop a comment below or reach out on GitHub!


Tags: #react #authentication #opensource #flask #jwt #oauth #webdev #javascript #python #nextjs #security #selfhosted #indiedev #auth0alternative #clerkalt

Top comments (0)