How Pipestreamr replaced Clerk in a single prompt
Authentication is one of those things every SaaS needs but nobody wants to build from scratch. For a long time, PipeStreamr used Clerk — it worked, but it was a heavy dependency. A hosted auth service means another vendor, another dashboard, another bill, and another thing that can go down and take your login flow with it.
We'd been eyeing FutureAuth as a replacement. It's a Rust-native auth library that runs inside your own backend — no external service calls during login. OTP-based email authentication, session management, and user lookup all happen against your own Postgres database. It ships as a crate with first-class Axum integration, which is exactly what PipeStreamr's backend runs on.
The question was: how painful would the migration be?
One Prompt, Full Migration
Here's the thing about coding agents — they're not just good at writing new features. They're excellent at migrations, because migrations are mostly mechanical: rename columns, swap imports, update middleware, rewrite the auth extractor, and make sure nothing breaks.
We gave Claude Code a single prompt:
Migrate our auth from Clerk to FutureAuth. Use the futureauth crate with
axum-integration. OTP email login, sessions stored in Postgres, replace
clerk_id with futureauth_user_id everywhere.
The agent:
- Added the dependency —
futureauth = { version = "0.3.2", features = ["axum-integration"] }inCargo.toml - Wrote the database migrations — renamed
clerk_idtofutureauth_user_idin the users table, with FutureAuth's own session and user tables created viaensure_tables - Rewired the auth extractor — replaced the Clerk JWT verification with a cookie-based session lookup against the local
futureauth_sessionstable - Mounted the auth routes — FutureAuth's axum router provides
/send-otp,/verify-otp,/session, and/sign-outout of the box, so the agent just merged it into our existing router - Updated the frontend — swapped out the Clerk React components for simple email-and-code forms that hit the new endpoints
- Handled the user linking — wrote logic to match existing users by email so nobody lost their data during the migration
All of this happened in one session. The agent read the existing Clerk integration, understood the data model, and systematically replaced every piece.
What FutureAuth Actually Does
FutureAuth takes a different approach from hosted auth providers. Instead of redirecting users to a third-party login page or verifying JWTs against an external API, everything runs locally:
- OTP email login — users enter their email, receive a code, and verify it. No passwords to hash or leak.
- Server-side sessions — sessions live in your Postgres database. No JWTs to decode on every request, no token refresh dance.
- Axum middleware — drop
futureauth::axum::auth_router()into your router and you get all the auth endpoints. The session cookie (futureauth_session) is set automatically. - Self-hosted — your auth data never leaves your infrastructure. One fewer external dependency to monitor.
The entire integration in PipeStreamr's backend is about 10 lines of setup code:
let auth = FutureAuth::new(db.clone(), FutureAuthConfig {
api_url: env::var("FUTUREAUTH_API_URL")
.unwrap_or_else(|_| "https://future-auth.com".to_string()),
secret_key: env::var("FUTUREAUTH_SECRET_KEY")
.expect("FUTUREAUTH_SECRET_KEY must be set"),
});
Then the auth routes get merged into the Axum router:
.merge(futureauth::axum::auth_router(state.auth.clone()))
That's it. Login, logout, session validation — all handled.
Why This Matters for AI-Assisted Development
The FutureAuth migration is a good example of the kind of task where coding agents shine. It wasn't a creative problem — it was a systematic one. Every file that referenced Clerk needed to be found and updated. Every database column needed renaming. Every auth check needed rewriting. A human would spend a day on this and probably miss something. The agent did it in minutes and caught every reference.
This is the pattern we keep seeing at PipeStreamr: the best use of a coding agent isn't writing code from scratch — it's doing the tedious, wide-reaching changes that humans put off because they're boring and error-prone.
We went from "we should probably switch off Clerk someday" to "it's done" in a single terminal session.
Links
- FutureAuth — Rust-native authentication
- PipeStreamr — Unified events API
- Claude Code — AI coding agent