convex-logto

Why this package

What it does that hand-rolling doesn't, and when you should use something else instead.

If you already run Logto, this page is the honest version of "should I install this". It should still be useful when the answer is no.

When you should use something else

You have no auth provider yet, and no reason to want Logto. Use @convex-dev/auth. It lives in your Convex deployment, owns the users table, and does OAuth, magic links, OTP and passwords without a second system to run. This package assumes Logto already exists; it does not make a case for adopting Logto.

You want a hosted provider and don't need self-hosting. Convex ships first-party integrations for Clerk (convex/react-clerk) and Auth0 (convex/react-auth0). Both are better supported than anything a single maintainer publishes. Logto's argument against them is that you can run it yourself, on your own data; if that is not an argument you need, take the first-party path.

You need organization permissions, and organization roles are not enough. Logto issues fine-grained organization permissions only in an organization token, audienced urn:logto:organization:{id}, which Convex will not accept as a request credential. Membership and organization roles work here (they ride in the ID token); permissions do not. See the token custody ADR.

What bridge mode saves you

Not that much, and it is worth being clear about it. Convex accepts any OIDC provider through auth.config.ts, so wiring Logto by hand is a real option. What you would write yourself:

  • A useAuth bridge from @logto/react to ConvexProviderWithAuth, including the force-refresh path, which has to clear the access token to make Logto rotate the ID token, and the one-render loading pulse that otherwise shows every user a logged-out flash right after sign-in.
  • { domain, applicationID } in auth.config.ts, which is two lines.
  • The discovery that Logto's default OIDC signing algorithm is ES384, which Convex rejects. This costs an afternoon the first time; the package cannot fix it either, but it tells you up front (see Bridge mode).

If bridge mode were all this package did, "just write it yourself" would be reasonable advice.

What is hard to write yourself

Session mode, which is what the Quick start sets up. The Logto refresh token lives in a Convex component, in tables your own app code cannot read; the browser holds a short-lived ID token and a rotating application session token. That buys live revocation, a "where am I signed in" list, and a browser that holds nothing long-lived, on a static CDN deploy, where no same-site HttpOnly cookie is reachable at all.

The reason this is not a weekend project is the failure modes, not the happy path. A Logto refresh token must never be presented twice; reuse detection destroys the whole grant, including sibling sessions that share it. So a refresh whose outcome is unknown cannot be retried and cannot be discarded. The component must never classify a deployment misconfiguration as a dead session, or one wrong environment variable deletes every session in the deployment, one refresh at a time. And the component has to persist a rotated token before it raises the failure that reported it. Those rules are written down as invariants, each with a regression test.

Revocation that arrives before the token expires. An ID token stays valid until it expires no matter what you do at the provider. Session mode subscribes to session liveness, so a revoked session drops the client live rather than up to an hour later.

Webhook sync and back-channel logout need signature verification, replay containment, and bounded bodies. They also carry the specific decision to tolerate Logto payload drift rather than reject it, because Logto retries a 5xx and not a 4xx, so a strict validator turns a schema change into silent event loss.

The shape of the promise

  • One provider on the frontend. No hand-rolled useAuth.
  • One line on the backend. No JWT template, no algorithm, no JWKS URL.
  • Every Logto value on the Convex deployment. In session mode the frontend build carries no Logto values; the only per-environment value in the bundle is the Convex URL.
  • Bridge and session mode present the same ID token to Convex, so identity, webhook sync, and environments are the same downstream, and moving between them is a new Logto app and a provider swap.

Status

Pre-1.0, so breaking changes land in minor versions until 1.0. The roadmap issue tracks what "1.0" means and what is still missing.

On this page