Alan WestThe story behind Authon — a free, self-hosted auth platform with 15 SDKs supporting OAuth, MFA, Passkeys, and Web3
Authentication is the first thing every web app needs and the last thing anyone wants to build from scratch. So we used Clerk. Then we used Auth0. Then we tried Supabase Auth. Each time, we ran into the same problems.
This is the story of why we built Authon — a free, self-hosted authentication platform — and open-sourced it.
Clerk charges $0.02 per monthly active user beyond 10,000. Auth0's Essentials plan charges $0.07/MAU. At 50,000 users, you're looking at $800-3,500/month just for authentication.
For a bootstrapped startup, that's painful. Authentication is infrastructure — it should scale with compute cost, not user count.
| Users | Clerk | Auth0 | Authon |
|---|---|---|---|
| 10,000 | Free | ~$700/mo | Free |
| 50,000 | ~$825/mo | ~$3,500/mo | Free |
| 100,000 | ~$1,825/mo | Custom | Free |
| 500,000 | Custom | Custom | Free |
When your middleware imports @clerk/nextjs, your React components use <ClerkProvider>, and your API routes call clerkClient.users.getUser(), switching auth providers means rewriting most of your application.
Firebase Auth is worse — your Firestore security rules reference request.auth, your Cloud Functions use auth triggers, and your user IDs are Firebase-specific. Migrating away is a multi-week project.
When you use a hosted auth service, your users' credentials, sessions, and personal data live on someone else's servers. For some industries and regions (healthcare, finance, EU), that's not acceptable.
We needed three things that no single platform offered:
We built Authon as a standalone auth platform with these principles:
Authentication methods:
Security:
Platform features:
| Platform | Package |
|---|---|
| Vanilla JS | @authon/js |
| React | @authon/react |
| Next.js | @authon/nextjs |
| Vue 3 | @authon/vue |
| Nuxt 3 | @authon/nuxt |
| Svelte | @authon/svelte |
| Angular | @authon/angular |
| React Native | @authon/react-native |
| Node.js | @authon/node |
| Python |
authon (PyPI) |
| Go | authon-go |
| Dart / Flutter |
authon (pub.dev) |
| Swift (iOS/macOS) |
Authon (SPM) |
| Kotlin (Android) |
authon-kotlin (Maven) |
| CLI scaffolding | create-authon-app |
We didn't want to ship an SDK for React and leave everyone else to figure it out. Whether you're building with Django, FastAPI, Gin, Flutter, or SwiftUI — there's a first-party SDK.
import { AuthonProvider, SignedIn, SignedOut, UserButton, useAuthon } from '@authon/react';
function App() {
return (
<AuthonProvider publishableKey="pk_live_...">
<SignedIn>
<UserButton />
<Dashboard />
</SignedIn>
<SignedOut>
<SignInButton />
</SignedOut>
</AuthonProvider>
);
}
function SignInButton() {
const { openSignIn } = useAuthon();
return <button onClick={openSignIn}>Sign in</button>;
}
That's the entire auth integration. The openSignIn() function renders a ShadowDOM modal with every provider you've configured in the dashboard.
import { authMiddleware } from '@authon/nextjs';
export default authMiddleware({
publicRoutes: ['/', '/sign-in', '/pricing'],
});
export const config = {
matcher: ['/((?!_next|.*\\..*).*)'],
};
Three lines of config. Every non-public route is protected.
import { expressMiddleware } from '@authon/node';
app.use('/api', expressMiddleware({
secretKey: process.env.AUTHON_SECRET_KEY!,
}));
app.get('/api/profile', (req, res) => {
res.json({ user: req.auth });
});
The middleware verifies the JWT, decodes the user, and attaches it to req.auth.
const authon = new Authon('pk_live_...');
// Get a nonce for the wallet to sign
const { message } = await authon.web3GetNonce(address, 'evm', 'metamask');
// User signs the message in MetaMask
const signature = await ethereum.request({
method: 'personal_sign',
params: [message, address],
});
// Verify the signature and get a session
const user = await authon.web3Verify(message, signature, address, 'evm', 'metamask');
No third-party plugins, no complex integrations. Web3 auth is built into the core SDK.
Most auth solutions inject UI directly into the DOM or use iframes. Both have problems:
* { box-sizing: border-box; } rule, a global font override, or a high z-index div can break the login form.Authon uses ShadowDOM. The modal is rendered inside a shadow root that's completely isolated from your app's styles. Your CSS can't leak in, and the modal's styles can't leak out. It just works, every time.
| Feature | Authon | Clerk | Auth0 | Auth.js | Firebase Auth | Supabase Auth |
|---|---|---|---|---|---|---|
| Self-hosted | Yes | No | No | N/A (library) | No | Yes |
| Free unlimited users | Yes | No (10K) | No (25K) | Yes | Partial | Partial |
| Web3 | Yes | No | No | No | No | No |
| Passkeys | Yes | Yes | Yes | Experimental | Limited | No |
| MFA | Yes | Yes | Yes | No | Paid | Yes |
| Organizations | Yes | Yes | Yes (paid) | No | No | No |
| ShadowDOM UI | Yes | No | No | No | No | No |
| SDKs | 15 | 8 | 20+ | 5 | 10+ | 6 |
| Pre-built UI | Yes | Yes | Universal Login | No | FirebaseUI | Basic |
Authon SDK is MIT licensed and available on GitHub. The monorepo contains all 15 SDKs, fully typed, with complete documentation.
git clone https://github.com/mikusnuz/authon-sdk.git
cd authon-sdk
pnpm install
pnpm build
The fastest way to try Authon:
npx create-authon-app my-app --framework nextjs
This scaffolds a Next.js app with Authon pre-configured — provider, middleware, sign-in page, and dashboard.
Or install manually:
npm install @authon/nextjs @authon/js
Documentation: docs.authon.dev
GitHub: github.com/mikusnuz/authon-sdk
Website: authon.dev
We're actively building:
If you're tired of paying per user for authentication, or you need Web3/Passkeys/MFA in a self-hosted package, give Authon a try. We'd love your feedback.