- Your M2M client count (or Auth0 MAU) has outgrown what the tier price pays for.
- You are building AI agents and Auth0’s per-connection M2M model does not give you delegation chains, per-agent rate caps, trust scoring, or cost attribution.
- You want an MCP OAuth 2.1 server wired into the same auth instance rather than a second system.
- You want GDPR data export tooling and self-hosted data residency on the same platform that issues your tokens. theAuth gives you the building blocks, it does not certify your deployment against any regulation.
- You rely on Auth0’s Universal Login branding, device enrolment flow, or the Guardian MFA app. theAuth ships headless building blocks, not a hosted page.
- Your Rules or Actions logic runs heavy third-party integrations (Okta, Duo, Risk signals). You can port that logic into your own route handlers, but it is not a copy-paste job.
- You are on Enterprise with dedicated support SLAs. We do not have a 24/7 hotline.
Concepts map
Server setup
Role-based access is handled by the built-in policy engine (
theauth.policy.evaluate), there is no separate rbac plugin to opt into, see Policy engine. The MCP OAuth 2.1 server is not a plugins: [...] entry and not just a config key: you build it with createMcpModule from @glinr/theauth/mcp and pass it to the adapter, see MCP. theauth.org exists only when you pass org: {...} to createTheAuth; the organization() plugin on its own only adds the HTTP endpoints.Mounting the handler
Auth0 hides its auth server behind the hosted domain. theAuth exposes an explicit handler on your own domain. Pick your framework adapter.Client code
<TheAuthProvider>, then the hooks work the same way they do in the Auth0 SDK.
Rules and Actions
Auth0 Rules ran in a VM on their side. Actions replaced them with a saner runtime. theAuth has no equivalent of the post-login trigger: there is no sign-in, sign-up, or session hook. Thehooks option on createTheAuth is for agents only, and the real hooks are beforeAuthorize, afterAuthorize, beforeAgentCreate, afterAgentCreate, onAgentRevoke, and onViolation (see Lifecycle hooks).
For the common “add a custom claim to the token” Action, use the customClaims option of the JWT session module:
customClaims receives { id, email, name } and returns extra claims for the access token. Anything else an Action did after login (provisioning, notifications, blocking a sign-in) goes in your own route handler around theauth.username.signIn. For agents, hooks.beforeAuthorize can veto a call by returning { allow: false, reason }:
Machine-to-Machine clients
Auth0’s M2M clients issue long-lived tokens against an API. They have a flat scope list, no delegation, no per-client rate cap.- Rotation is atomic. Old token dies the moment the new one is issued. No gap.
- Per-agent rate caps (
maxCallsPerHour), approval gates (requireApproval), time windows (timeWindow), and IP allowlists (ipAllowlist) are part of the permission’sconstraints, not a separate rule.
User data migration
Auth0 exports users via the Management API bulk export, which delivers JSON lines. theAuth users live in your own Postgres, SQLite, MySQL, or D1, in thetheauth_users table that createTheAuth creates for you. Do not create your own users table.
Three things to know about the shape of the data:
- Human users are rows in
theauth_users(id,email,name,email_verified,metadata,force_password_reset, timestamps). - Password credentials are rows in
theauth_username_accounts(user_id,username,password_hash). Sign-in looks up theusername(lowercased by default) and does not apply the sign-up pattern check, so storing the lowercased email as the username lets users keep signing in with their email. - Social links are rows in
theauth_oauth_accounts(provider,provider_account_id,access_tokenwhich isNOT NULL, so use an empty string for imported links).
legacy_password_hashes table and writes an unusable placeholder into theAuth, with force_password_reset set so nobody can sign in until they either reset or are rehashed:
- Forced reset (simplest). Configure the
passwordResetmodule oncreateTheAuth(it needsresetUrlandsendResetEmail), then send each user throughPOST /auth/forgot-password. The placeholder account row is what lets the reset flow find them, and a successful reset clearsforce_password_reset. - Lazy rehash in your own sign-in handler. Verify the bcrypt hash yourself, write a PBKDF2 hash in theAuth’s format, then call
theauth.username.signIn:
legacy_password_hashes and force a reset for anyone who never signed in. Social-only users have no hash: wire up the same provider in the oauth plugin before the cutover, and insert their theauth_oauth_accounts link (Auth0 ids look like google-oauth2|1234, the part after the pipe is the provider_account_id).
Cutover plan
- Stand up theAuth next to Auth0. Point at the same user database schema. Do not turn off Auth0 yet.
- Import users. Validate a sample with a test sign-in.
- Point a single route (for example
/api/admin) at the theAuth handler. Keep everything else on Auth0. - Watch the audit log for authorization failures. Fix mismatches.
- Expand the theAuth-guarded surface one route at a time. M2M clients cut over last, after you have confirmed rate caps and approval gates in staging.
- Final DNS or reverse-proxy switch: all auth traffic hits theAuth. Decommission the Auth0 tenant after the grace period.
Rollback
Keep the Auth0 tenant active for at least one full session-refresh cycle after the cutover (default 7 days). If you need to revert:- Flip the reverse proxy or middleware back to Auth0.
- Users stay signed in using Auth0 cookies. New sessions come from Auth0 again.
- theAuth sessions remain valid locally until they expire. No user-facing disruption.
What Auth0 does that theAuth does not (yet)
- Hosted Universal Login. theAuth ships React, Vue, and Svelte components you host yourself.
- Guardian push-MFA mobile app. TOTP and passkeys are supported, push MFA is not.
- Attack Protection suite (bot detection, breached-password detection on their edge). theAuth has HIBP integration and IP rate limiting, but no managed bot detection.
- A hosted B2B customer-admin console. theAuth has an embeddable
@glinr/theauth-dashboardReact component for operators, not a hosted SaaS console.
Runnable example
A minimal, in-memory version of the AFTER patterns lives atexamples/migrate-from-auth0. It pulls @glinr/theauth via the monorepo workspace:* protocol so the script stays pinned to the same version the docs describe, and the smoke test runs in CI on every push.
Next steps
Agent identity
Model M2M clients as agents with delegation and trust scoring.
MCP OAuth 2.1
The authorization server Auth0 does not ship.
Lifecycle hooks
The real hook points (agent authorization and creation) that replace per-agent Actions.
Audit trail
Tenant logs, queryable and exportable, written for every authorize() call.