Moving off Auth0 or Clerk to an EU-resident identity provider
The conversation usually starts when somebody in legal or security asks where your end users' identity data lives, and "we selected the Frankfurt region" turns out not to be the answer they were after.
A US-headquartered provider can be compelled under US law to hand over data it controls, and the location of the disks isn't what decides that. It's a question about control, which an availability-zone dropdown doesn't answer. There's a second question worth putting to any vendor including us: your region setting governs the user store, so what governs the configuration, the admin console, the audit records, the support tooling? Users in Frankfurt and a control surface somewhere else is a common shape, and most vendors will tell you which they have if you ask.
Where the data actually sits
Our data plane holds end-user identity (user records, sessions, refresh tokens, magic-link attempts, TOTP enrolments) and serves every end-user request without a call through the control-plane backend. That covers sign in, verify, refresh, log out, token exchange and JWKS.
The auth control plane is the configuration source of truth: apps, redirect URIs, signup toggles, mailer config, branding, per-app signing-key records, the operator audit trail. It isn't the system of record for end-user identity.
Since anyone can claim to have separated their services, here is what actually holds the line:
cp_auth_controlandcp_auth_dataare separate databases, owned by separate Postgres roles.- Where the data plane reads control-plane configuration it connects as a third role,
cp_auth_data_reader, which holds column-scoped grants on four named tables and nothing else. Not table-wide, not schema-wide: it can readapps(id, project_id, slug, status, signup_enabled), for instance, and not theclient_secret_hashsitting in the same row. - Where those grants do cover secret material, it's ciphertext. The reader can select an app's wrapped signing key, because signing tokens is the data plane's job, but the key that unwraps it is not in that database.
- Connection pools are phantom-typed by plane in Rust, so a store that needs the control-plane pool will not accept the data-plane one. Wiring the wrong pool into the wrong service fails to compile rather than failing at runtime against the wrong database.
- The control-plane backend stays off the data path. An operator viewing users in the dashboard has their browser call the data plane's admin API directly, cross-origin, with a short-lived project-scoped token that the control plane exists to mint.
That column-scoped grant is a direct read of the control-plane database, so the split isn't a claim that the two databases never touch. What it buys is that the control-plane service is never in the request path and never holds end-user records.
Operator identity is the other thing to be clear about. Your team's admin accounts, their sessions and their MFA enrolments all live in the control plane. The claim on this page is about end-user identity specifically.
On the hosted plan today both planes run in our infrastructure, so the split is about system-of-record and blast radius rather than about your users' data never reaching our network. The data plane is the piece built to be deployed onto your own infrastructure, which is something we set up with you rather than a download.
Our own deployment sends mail through a relay we run, on Postgres and Redis we run, rather than through third-party APIs for either. The site you're reading loads no third-party CDN, font or analytics script, which you can check from your devtools right now.
The migration is mostly OIDC
We're an OpenID Connect provider: discovery, JWKS, authorization code flow with PKCE, confidential and public clients. One limit to know before you plan around it: a public client can only register loopback redirect URIs today, so a standalone browser SPA on a real domain isn't one yet. Browser integrations go through the Next.js SDK's server routes instead.
/v1/auth/{project}/apps/{app}/.well-known/openid-configuration
/v1/auth/{project}/apps/{app}/.well-known/jwks.json
/v1/auth/{project}/apps/{app}/authorize
/v1/auth/{project}/apps/{app}/token
/v1/auth/{project}/apps/{app}/userinfo
If your app reaches Auth0 through a generic OIDC client, the issuer URL and a client ID and secret are the minimum protocol-level changes, but claim mapping, scopes, logout behaviour and anything built against a vendor management API won't come across for free. Stock Auth.js v5 completes the whole flow against us, discovery through session, with no platform-specific code in your app.
Clerk is a different shape of job because its drop-in React components are the integration. We ship a Next.js App Router SDK with the equivalent pieces (AuthProvider, SignIn, SignUp, SignedIn, SignedOut, useUser), plus a headless entry point for your own UI and a server module for route handlers and middleware. Budget it as a port.
Password hashes can't come across at all, because there's nowhere to put them: our users table has no password column, by design. Existing accounts import by email and go passwordless on their next sign-in. Decide that before the cutover, and tell your users sign-in has changed before they find out at the login screen.
The one thing we don't do
Social login. No "Sign in with Google", and it isn't a gap we're working through: it's a decision recorded in an architecture decision record, and reversing it would take another one rather than a sprint. The reasoning is here. If you run a consumer product where one-click signup drives conversion, that alone should disqualify us.
What's next
Web is what we support today, and native apps are the next thing we're taking on. Custom-scheme redirects carry storage and credential questions we want to answer properly rather than quickly, so they're a deliberate next step instead of an oversight. Dynamic client registration is on the same list; for now an operator configures clients, which at the team sizes we're working with hasn't been anyone's bottleneck.
Worth knowing before you plan a cutover: none of this has to be a big bang. There's a bulk import on the admin API, 500 users per batch, idempotent on project and email so you can re-run it without duplicating anyone. It preserves the provider and provider user ID each account came from, so an account imported from Auth0 still knows it was an Auth0 account. Paste a batch into the dashboard or curl it. Most teams run both systems side by side and move cohorts across at whatever pace they want, rather than picking a weekend and hoping.
On certifications
Worth saying plainly, since it tends to come up early in a security review: as of August 2026 we hold no certifications and no audit is under way. Not ISO 27001, not SOC 2. At our size those follow the first customers who require them, and we'd rather tell you that than imply a posture we haven't earned. If it's a hard gate for you, say so early and we'll talk about what it would take.
If you're a European B2B product, an internal tool, or selling into public-sector or regulated buyers, the trade is that you give up social login and get an identity provider whose answer to "where does the data live" is structural instead of regional. Google sign-in is the one thing that isn't coming. Everything else is a date, so it's worth asking where we are on it.
Timonier is EU-resident authentication with a self-hostable data plane. Start with the Next.js quickstart, or email hello@timonier.eu about the data plane on your own infrastructure.