WorkOS Authkit as Custom Auth Integration with Convex
Currently having some trouble figuring out how to use WorkOS Authkit as Custom Auth with Convex.
- there doesn't seem to be an equivalent of an
AuthProviderXReactProvider
- not sure how to update the useAuthFromProviderX
reference to use with ConvexProviderWithAuth
so that ctx.auth.getUserIdentity()
works correctly
- not sure what to provide as the domain
and applicationID
in the auth.config.js
file
I've tried referencing this next-authkit-example, the convex clerk example, and the nextauth example by Web Dev Cody, but still not sure how to get it working with convex.
The WorkOS docs mention that:
In order to persist the authenticated state of the user in the application, we need to store and access a session. WorkOS User Management does not currently offer a session management feature, this must instead be handled by the application. For illustration purposes we’ll be using a JSON Web Token (JWT) to store the authenticated user in a short lived cookie, though your approach may differ depending on the application's specific requirements.Which seems to refer to this file in their example. Not sure if that affects the setup for Convex.
User Management – WorkOS Docs
Easy to use authentication APIs designed to provide a flexible, secure, and fast integration.
Custom Auth Integration | Convex Developer Hub
Convex can be integrated with any identity provider supporting the
User Management – WorkOS Docs
Easy to use authentication APIs designed to provide a flexible, secure, and fast integration.
GitHub
next-authkit-example/src/app/callback/route.ts at main · workos/nex...
Example application demonstrating how to authenticate users with AuthKit and the WorkOS Node SDK. - workos/next-authkit-example
223 Replies
Hey @John, from cursory look, I think you'd have to initiate a JWT on the server:
https://workos.com/docs/user-management/3-handle-the-user-session/issue-a-jwt
Then pass that to your client and pass that to Convex via the Custom Auth Integration.
Does that help?
Any reason you can't use Clerk or Auth0?
User Management – WorkOS Docs
Easy to use authentication APIs designed to provide a flexible, secure, and fast integration.
was mainly curious about workos since it says 1million free users
going to just using clerk, the templates/doc and web dev cody yt videos were great references!
@John if you find any solution do share it here . I am also transitioning from clerk auth to some other solution cause clerk auth is having too many bugs and their support is also slow.
Thanks
@John you might be able to use WorkOS through NextAuth: https://stack.convex.dev/nextauth
Convex with Auth.js (NextAuth)
Learn how to use Auth.js with your Next.js server and Convex backend to build a full-featured authentication system.
@Michal Srb - When you say work with WorkOS through NextAuth, I think you are saying to use WorkOS as a provider. I don't think that's going to help many Convex projects because businesses don't typically store their employee user info in WorkOS.
Can I request a feature so that WorkOS is an officially supported third-party authentication provider for a Convex project?
In the meantime, I'll try to setup WorkOS through the Custom Auth Integration approach.
https://docs.convex.dev/auth/advanced/custom-auth
https://docs.convex.dev/auth/advanced/custom-auth
Custom Auth Integration | Convex Developer Hub
Note: This is an advanced feature! We recommend sticking with the
Actually, let me do some more research before going so far as to request a feature for Convex's official WorkOS support.
it seems at the moment the first blocker I am facing is, workOS ISS=https://api.workos.com while the actual issuer is something like this https://xxxxxxxxxxxxx-staging.authkit.app
so when I enter the correct one in convex, convex tries to match it to iss filed and we get ""error":"No auth provider found matching the given token""
I even tried to tamper with the JWT by changing the iss and signing it again, but obviously this wont work as WorkOS will not be able to verify the original sig
@Matt Luo convex has to solve it by changing how they match issuer, or making it configurable in
ConvexProviderWithAuth
I spoke to workOS, they are very reluctant to do anything about it as it would be a breaking change for themWould they be open to setting up a redirect for the oidc endpoints from api.workos.com to the actual issuer?
If their JWT's aren't compatible with oidc that feels like something worth addressing on their end. Redirect would be non-breaking.
We are also trying to get workOS authkit to work and can't do it either!
* We are able to decode the JWT token, but Convex’s “custom JWT” provider accepts only Content-Type: application/json
WorkOS returns application/jwk-set+json
Support for this content type is on the way
@imad @erquhart so there is no way to use workos with convex right now?
I was going to setup that
I believe support for the content type mentioned by Bruce is now accepted, so it should work if that's all that was blocking.
If you have a working WorkOS implementation with Convex, please share what you learned!
I am still getting the same error
With this config
I followed this guide https://docs.convex.dev/auth/advanced/custom-jwt
Custom JWT Provider | Convex Developer Hub
Note: This is an advanced feature! We recommend sticking with the
Error when trying to authenticate:
Ah the issuer not matching the issuer in the token would still be a problem
Actually, you'll want to determine what the issuer is in the token (you can parse it at jwt.io), and use that for the issuer field. Then you need to determine what the actual jwks endpoint is and provide that under jwks field. @imad mentioned it's something like https://xxxxxxxxxxxxx-staging.authkit.app/
this is the decoded payload
With custom jwt no oidc discovery is done, so the issuer is just being used to validate the token.
Gotcha, so the
iss
claim is what you want to use for issuer
And then you need to determine where the actual jwks endpoint is and provide that under jwksI am not using authkit from workos and I tried with this URL
But now getting this error
I tried with this online debugger and it is able to verify the token with same jwks url

Hey guys - I have a working Convex / WorkOS Authkit implementation so figured I'd share how I solved these issues:
Issue #1 - "No auth provider found matching the given token"
Use the customJwt format in your Convex auth.config.ts instead of the OIDC Provider format. Set issuer to
https://api.workos.com
or whatever custom domain you use.
Issue #2 - missing aud claim
For this you can simply go into the WorkOS Dashboard -> Authentication -> Custom JWT claims and add "aud". Call it whatever you want and make sure the value matches the issuer
property from your Convex auth config.
Issue #3 - "Invalid Content-Type when fetching"
This is really something WorkOS should fix on their side, but there's an easy workaround. Just create your own API route and use that for the JWKS endpoint. For example, here is a simple NextJS Route Handler exposed at /api/jwks:
Then you can use your own URL in the auth.config. Here is what mine looks like:
export default {
providers: [
{
type: 'customJwt',
applicationID: 'gizmo',
issuer: 'https://api.workos.com',
jwks: 'https://your-app-url.com/api/jwks',
algorithm: 'RS256',
},
],
};
Hope this helps!Wow! amazing! thank you!
Thanks for sharing this!! The content type should be supported, are you running self hosted by chance?
Nope. And I'm on the latest version of Convex but it was still throwing an error with application/jwk-set+json being returned by WorkOS in the header.
Also, another issue is when you use the method I posted above and install any Component, your push will fail because of a Zod Error for "appAuth.domain" being undefined. Even if you add domain: '', as long as the type is still customJwt it throws.
So, I had to do an even more complex custom OIDC implementation in my own backend to get all of this working. Just a heads up in case that's something the team can address!
Not sure on the Zod error but the content-type support was deployed yesterday, should work now
Ah, there it is: https://github.com/get-convex/convex-js/blob/061c176c2fbe98eaf16dbd003d9e106277f61ac4/src/cli/lib/deployApi/types.ts#L7-L10
I'm having some trouble getting this to work. Would appreciate any help figuring out what's going wrong.
My auth.config.ts:
The token that I manually verified (being sent down by the websocket)

was just going to ask for that
hmm
aud needs to match your applicationID
That worked! I swore I tried that but I guess in all the combinations I didn't. Thanks for your help
Hello, Would you have an example with the front end? I am banging my head to be able to plug it.
for nextjs I mean
I have this
auth.config.js
:
WorkOS JWT Template page:
/api/auth/jwks
:
But when I try to access this from a query:
it always returns null
Trying to summarise my implementation so far
Client providers
Nexjs routes
Request magic auth code
Sign in route and storing session as cookie under "wos-session". I can see the cookie properly setup in the browser.
jwks route. I can see convex calling this endpoint and the data being returned.
convex auth.config. I have deployed the repo to vercel to get the jwks url live so convex can access it but using the same workos client id than development.
And of course the JWT template in workos
lol.... the trailing slash on the issuer of the convex auth.config was the issue. I edited the code above and now you got a working custom integration of Authkit with convex for otp via email!
for completion sake, below the methods calling the nextjs api routes you can plug with your form.
I also removed the nextjs endpoint for jwks which is not necessary anymore.
And on your auth form, don't forget to call the refreshAuth method below to update the AuthKit provider after the cookie has been set by the server api route via the saveSession helper on the browser. Otherwise, a hard reload of the page is necessary for the convex client to pickup the authentication state.
here is a repo I put together with detailed explanations for the setup
authkit-convex repo
GitHub
GitHub - sbkl/authkit-convex
Contribute to sbkl/authkit-convex development by creating an account on GitHub.
I'm using magic auth too, thanks for sharing this! I'll give it a shot.
It looks like I'd have to use authkit and ditch my custom designed login pages if I wanted this to work in my setup
this example is made especially for custom designed login pages. Not using workos urls.
Ah my bad I missed the AuthForm!
Just updated the repo to add google oauth example. Make sure to add "authentication.oauth_succeeded" to your webhook.
We can fix this, we already normalize some other trailing slashes because some providers tell you to use one when they shouldn't
great work @sbkl, just catching up here
this kind of thing https://github.com/get-convex/convex-backend/blob/35faa2b8e8b18b1814846b0ec2e003c5599ce2c3/crates/common/src/auth.rs#L64-L79
huh, this should already be normalizing slashes? need to look into it later
@ballingt Do you think workos will become one of the officially supported 3rd party solutions in the future for auth or probably not?
Sure! We've chatted with them back when we only had OIDC support about what to do, decided to go wtih custom JWT
"Official" here would mean
- mention them in docs? yeah definitely
- have a quickstart in the docs, include it in
npm create convex@latest
wizard? sure, need to get around to it
- include custom code for them in the core client? we stopped adding auth providers to the core package years ago so they'd (or we could in a separate package) manage the client code, but there's not much of it; in e.g. React, it's just "create a convex auth provider and pass in a useAuth hook;" but we do need to at least show that code somewhereYeah even if not "official" maybe some mention in the docs like you said and a quick example/overview of the steps.
About to start using convex for first time, going to try with workos
we also need to buff up the auth error mesages, well done everyone in this thread debugging but it's hard when it just says "no auth provider found," or "can't validate;" we dont' want to reveal info inadvertently about a deployment's auth config but we could at least show extra info in the dashboard
If someone can repro the missing slash issue I'd appreciate it, happy to normalize that, just need to find it
And just integrated SSO in the example. Just tested with the Test organisation included in your workos stage env.
For sure not perfect but a good base to start from.
If you fork, the repo I shared and add the trialing slash on the issuer, you should be able to reproduce it.
Sharing some pics with the form. Repo now includes magic auth with email OTP, google auth and SSO.


Got it, I haven't cloned the repo yet but repro'd it in our internal tests of customJwt behavior.
GitHub
Normalize trailing slashes in customJwt issuer and JWT
iss
(#3849...GitOrigin-RevId: 1d90ab9497e377ae8745c44ed55ffeafc6670966
For some reason, my token seems to just expire after a few minutes of being logged in and I get
Uncaught ConvexError: Authentication required - no identity found
Ok I resolved it by calling await refreshAuth();
and also setting up the query client as per here - https://github.com/sbkl/authkit-convex/blob/main/src/components/providers.tsx#L20-L34
Hmm it still appears once or twice but not as bad as it was before
Ok I see what it is. I need to adopt this pattern:
This seems to get around a race condition in the auth.Other versions of this include
<Authenticated>
component which doesnt render it's children until the connection is authenticated and
const { isLoading, isAuthenticated } = useConvexAuth();
True. This is not the best DX because for me part of the point of convex was to not have to do this stuff manually with react query. I'd personally love a WorkOS component or a more first-class integration. @sbkl's implementation is a great start though, I have some way of implementing protected queries/mutations now.
Ok looks like that didn't help either. I notice if I hard refresh it goes away for a while.
I'm wondering if I need to implement refreshAuth like this: https://discord.com/channels/1019350475847499849/1202520608672317471/1387692134878023710
Maybe I need to use this at the root level of my authenticated part of the app
Ok I made a custom auth guard using
useConvexAuth
and then a custom loader to display instead of the blank screen and I think that should do it. Thank you!I think this could be better, both a WorkOS guide and protecting authenticated queries.
The gist is
- some convex queries requires auth, some don't
- the client doesn't know which are which
- the client sometimes (it's a race) ends up sending in requests to subscribe to queries before sending in auth
lots of potential fixes, one would be having an "authed mode" for the client where it doesn't subscribe to anything until after auth has been sent in
That gist is spot on. Also, I'm happy to help you guys in any way on my own time to make this WorkOS experience easier. Just let me know. I was planning to boilerplate some of this for my own projects anyway. I'll have a lot more need for WorkOS in the near future. So anything officially recommended by Convex would really help me and I can put in the time for it.
lol exactly my thought. Started to work on a cli command like shadcn where it provides the working setup and all the code. And you change it as you please.
1- Install latest nextjs
2- Initialise shadcn
3- Install the necessary dependencies for the authkit nextjs convex integration to work for authentication (sso, oauth, magic auth) and webhooks to sync users and organisations tables (hono available as opt in or regular convex http router).
4- Copy the templates for all necessary config, functions, provider for nextjs and a custom form using shadcn components, tanstack form etc...
Still early but looking good so far to start a brand new project with all the auth bit setup easily
Video demo attached.
One question @ballingt ,
npx convex dev
is the only way to create a new convex project right?
Currently the setup I have requires to have all env variables setup (WorkOS keys, redirect paths...) under the convex dashboard of the project otherwise zod will fail the deployment. Is there a command where I can just create a convex project then have the cli ask for the mandatory api keys so I can set them up with npx convex env set
?
Or I am thinking to make the project creation in the convex dashboard as a prerequisit and then ask for the convex slug before initiating the cli.Basically a command that would just init the project in your convex account and return the convex slug without deployment and code gen so I can continue requiring the keys and set them up. Once completed, you just cd into the project and run npx convex dev and you’re good to go
You might find this script that sets up some Convex Auth stuff interesting, for how it gets through some of this https://github.com/get-convex/convex-auth/blob/main/src/cli/index.ts
this looks great!
would this flow work?
1. create (provision + write .env.local) + run codegen (just creating the convex/_generated/* files), but don't push code
2. read values from .env.local
3. push code
What you're asking for does exist (you can provision call the same endpoint that the convex CLI does) but would rather make it a convex CLI command
looks right!
if this is possible then perfect. Would just need to run that command.
thank you for pointing me on this. Going to steal some stuff from it.
Here's something you can do today:
Works like a charm!
There's some related work coming up around being able to deploy multiple dev deployments per person, will roll looking into a "create without pushing" flow into that. One option is (yet) another flag like
npx convex dev --no-push
, which would just be a npx convex codegen
if you already have a deployment but would create one if you don't.If possible to include a single command to set multiple env variables as well. It is marked as a todo on the convex-auth package.
I am almost done with a first draft. Just reviewing webhooks because you cannot possibly have a webhook secret ready when initialising a project since you need the convex slug for it. So thinking on how to sequence it since having users/organisations synced between workos and the convex database is critical to me.
@sbkl thank you for your repo, it's truly a gem!
I wanted to know whether the following is possible
I plan to run two webapps for my project.
One is the admin dashboard panel
And the other is the main site
looking at how workos is implemented through the repo
It seems like this wouldn't exactly work unless I'm wrong?
I'm desperate to switch out from clerk to workos
Working on a project right now that has a turborepo monorepo with a web app, mobile app and backend package with convex. The backend package is used across both apps. You could do the same with your use case of two web apps.
Ah yes thank you! This would be perfect actually
I'll start changing my repo for this
Keep your workos code as separate as you can from your regular convex functions. For the backend package, you could just use what @sbkl has as a base.
wait bit more on this
as im going to be using workos for both
main app and admin app
how do i sync the webhook url?
In
http.ts
. I just mean whatever workos stuff needs use node
keep those actions in separate files. Don't mix them with queries and mutations.Released a first draft of the command. Try
npx @sbkl/stack init
. Let me know what you think. Instructions in the repoI actually got it working in react router as well partly thanks to @sbkl 's example in nextjs if anyone is interested but the only thing I am not sure about is this part about revalidating the token, not sure if i am doing it correctly 🤔 there is no
useAccessToken
hook in the react-router package for authkit
https://github.com/developerdanwu/ai-storefront/blob/4a9eb19a3a752b8b626bdc99176c91e058dbc4f0/app/components/auth/auth-provider.tsx#L65-L95@sbkl Is there a way to get your setup working with react native?
I'm also getting page refreshes in my nextjs app when
isAuthenticated
is briefly false. Displaying a loading indicator, but it's random and hard to get around. I figure if I render null
the screen will just go blank.You might be able to use this https://workos.com/docs/reference/user-management/authentication/refresh-token inside of a convex action
API Reference – WorkOS Docs
Code snippets and type definitions for the WorkOS client libraries.
Now that I think of it, I might be able to do the same for react native
@Rishad B thanks for suggestion but i think i cannot directly get the refresh token in the FE 🤔 can u? revalidate the route data actually seems to work for me rn haven’t run into issues yet
authkit doesn't support react-native at the moment unfortunately. Got it working with convex-auth with their storage prop (and storageNamespace prop)
secure-store.ts
providers.tsx
So you can imagine building something similar using the user management api.
You could probably get it via
@workos-inc/node
inside a convex action and then return that value to the frontend
Awesome I will try this
What would the token setting side of things look like after login? I think I'm missing the full picture hereauthkit-js is the base package. It only supports browser based api which is not available to react-native. Therefore, it needs to allow for an optional storage props that would be used instead of the browser solution if it is provided.
Then you have authkit-react where there is a need for the authkit-provider to accept an optional storage props as well and link it to the authkit-js usage there.
Didn't look all the details but trying to make an implementation to make it working on my local machine and would submit a PR to both packages if they allow for it.
GitHub
GitHub - workos/authkit-js: Vanilla JS AuthKit SDK
Vanilla JS AuthKit SDK. Contribute to workos/authkit-js development by creating an account on GitHub.
GitHub
GitHub - workos/authkit-react: React SDK for AuthKit
React SDK for AuthKit. Contribute to workos/authkit-react development by creating an account on GitHub.
Is anyone getting some errors like this at times? I believe it is when the token refreshes and then the screen seems to blank for 1 sec while the token refreshes (My error message is custom but it basically means it can't find a authenticated user in convex BE)

Yeah I got something like that earlier in the thread. I left some comments around what worked for me. Every detail in this setup is important to avoid race conditions.
@Rishad B are you referring to this? but under the hood Authenticated already uses useConvexAuth, I don't see how that helps

wonder if it is not because of the hot reload
I think I found the error message in the sync websocket but idk how to fix it hmm

Anyone have a good summary of this thread right now? I want to use workOS with convex but it seems like it is issues?
@Rishad B made it work with expo. Using
expo-secure-store
, expo-auth-session
and expo-web-browser
.
storage.ts
providers.tsx
auth-form.tsx
convex public actions to get the authorisation url and authenticate with code
basically using convex actions to use the workos node package in a secure manner replacing the server component and api routes in nextjs. I know it's available in expo with some setup but wanted to keep it simple
btw the react-query part is optional but I like the api and use it everywhere.
need to make a similar approach for magicAuth and SSOI use magic auth but this looks like a good start for me to try
I've been using workos inside of actions to use it in both my web and mobile apps. I just couldn't get them protected like you just did. And my other protected queries and mutations were breaking. This will really help me, thanks! I'll post here once I've tested it.
If possible to include a single command to set multiple env variables as well. It is marked as a todo on the convex-auth package.@sbkl getting back to this, when would you want this to happen? Right before pushing the code, right?
Correct. So when pushing and executing the code, the env variables are there and the zod parsing the process.env variables making sure the variables must be set, doesn’t produce an error because they are missing.
This file helped me for my setup with expo, thanks! I handled the token refresh a bit differently though - separate provider that was refreshing in the background.
I wrapped the whole thing with a provider managing the entire logic of sign in via expo-auth-session, sign out, refresh token via expo api routes with a storage props using expo-secure-store
Update - I shared this thread with the founder of work os, he was super responsive on twitter/x.
Said they are working on it 🙂
Nothing official yet but this should help some people
https://github.com/workos/template-convex-nextjs-authkit
Am I the only one that gets:
Cannot find module '@workos-inc/authkit-nextjs/components' or its corresponding type declarations.ts(2307)
When tying to:
import { useAuth } from '@workos-inc/authkit-nextjs/components';
? 😄
Well it seems like installing the package without setting version to 2.4.3, installs some 0.4.3 version or something like this
Has anyone else noticed that everytime auth refreshes using the approaches in this thread, the loading state shows again briefly?
I feel like it should only be considered on initial page load and then do its refreshes in the background.
yeah, I also had the problem that the backend got called and I received unauthorized. Very weird
Would be nice for the preloaded query under
app/server/page.tsx
to also have a "protected" query example (throw error if ctx.auth.getUserIdentity() is null) and show how to get the token server side on nextjs. If you can make the feedback.
I actually believe the identitiy will always render null if you don't pass the accessToken.
For instance, the preloaded query example:
could should how to get the accessToken server side with AuthKit and how to pass it to convex query/mutation.
And the convex function would change from this:
To this:
Hi, I'm also currently investigating a Next.js App with Convex and WorkOS. The authentication on Convex Functions using
ctx.auth.getUserIdentity()
does work for queries in components that are children of the <Authenticated>
component.
My problem is that when WorkOS refreshes its access token (by default after 5 minutes), the app is briefly in a <Loading>
/ <Unauthenticated>
state which will unmount all components for a moment and then rerender, once the new JWT is validated by the Convex backend. This causes the problem that all local state of app components is lost due to the rerender after such an access token refresh. Does anyone else have the same problem?
I saw that Clerk also has a session token, that expires after 60 seconds. Is this reload thing a problem for users of Clerk. I looked into the implementation briefly.It's not a problem because their hook doesn't return "loading" while refreshing the token https://github.com/get-convex/convex-js/blob/main/src/react-clerk/ConvexProviderWithClerk.tsx
the convex client reads the
iat
/exp
or something (going from memory) to see when the token will expire, and tries to refresh the token 10 seconds? more? before the token expires to avoid a gap. Their auth hooks returns isLoaded: true
throughout this process, so no interruption. If the workos hook doesn't, then we should include in the wrapper hook logic to store that "hey this is a refresh, let's say we're not loading for a couple seconds" or similaryes i also noticed that. i tried to cache that the auth was loaded before and than only set the loading state for the initial loading. i will look into it again.
now that you mention it: the useAccessToken hook by WorkOS also automatically refreshes the token when its about to expire. Does Convex do the same? Whose responsibility is that really and shouldn’t I be able to deactivate this behavior from Convex, so the Auth provider will refresh the token only?
Oh interesting, right now the convex client will call the fetchAuth function 10s before expiring; I don't think it calls it with forceRefresh, so ideally that doesn't make a network call and just uses the refreshed token
@sbkl thank you for sharing your trials and tribulations with this. Were you (or anyone) able to get the workos OIDC example to actually work? ...or more specifically, retrieve the authed user's identity with
ctx.auth.getUserIdentity()
inside of a convex query?
Was able to get a JWT version working but am hitting the same refresh issues as @Niklas and the OIDC just seems a lot cleaner.
I just convinced my team to give convex a shot for a large refactor... and I am hoping that auth (literally the first thing we need to implement lol) isn't a blocker.GitHub
GitHub - workos/template-convex-nextjs-authkit
Contribute to workos/template-convex-nextjs-authkit development by creating an account on GitHub.
@Niklas if you use the custom JWT implementation, directly handle client side auth/ssr redirects with the official WorkOS packages and only ever use convex auth inside of queries and mutations then the following code seems to eliminate loading state issues:
@mwoof this is all workable stuff! might take a couple days but there's no fundamental blocker here. Is there a repo I can make a PR to to fix this, do you have an example, is it the one you just posted? (reading more carefully now)
@mwoof is this a workable solution for you, is there anything else you're missing? We'll get an example up in the next few days, I'm working with folks from WorkOS (well they're blocked on my merging their PRs, I'll do that) to make this smooth
Absolutely! My first post probably came across a bit harsher than I meant. I’ve been a huge fan of Convex for years, and you, @Tom , have solved more of my problems than I can count.
This solution works totally fine for now. Just a heads up though, WorkOS has recently released a few official videos and templates on integrating Convex, but they don’t explain how to secure data in the Convex database. Because of that, new users are being funneled into the Convex ecosystem without clear next steps on security (assuming thats the reason for a lot of the recent posts in this thread). A cleaner implantation might save you a headache or two (not that you need more on your plate lol).
If I can help with documentation—or anything else—just let me know. Happy to pitch in!
OH dont' worry, didn't sound harsh and harsh isn't a problem! Just want to make sure we get you set here, since this is already work we're doing it'd be a bummer if we just missed you
Yes I was able to get it work as OIDC. I contacted WorkOS Support and they added a feature flag for me. After that aud was sent without me needing to add it to jwt template. The only thing that was missing:
org id was not sent. I added this with the custom jwt template.
I will take a look at your refresh fix. This is the only thing currently not working for me.
Aud will then be the client id and the only thing that needs to be added in convex is:
If you were using custom domains or plan to use them in the future then you'll need to use the aud claim on your JWT template
and the convex/auth.config.ts file should look like:
I am using "useAuth" and "withAuth" only in NextJS for checking if routes are accessible etc. In convex I only use
const userIdentity = await ctx.auth.getUserIdentity();
I will test your implementation to see if it works for me. Thanks 🙂Did they mention if anything official with it was coming soon? Thanks for looking into it!
@Perfect I have no official comment from WorkOS but Tom said they are working with them. I guess until then contact the support for the feature flag or use the CustomJWT... Either way you need the CustomJWT to get the orgid in the identity. 🙂
The orgid is then attached to the user you get from "withAuth" in NextJS like this for example:
And in convex:
I think I’m just gonna wait until this settles down and something official comes out haha (at least for the feature flag function)
I’ve been piecing things together from various places, not sure i feel highly confident in the solution
I'm also kinda waiting for something official, I've been following this thread for a while and followed @sbkl 's instructions on getting the custom JWT working. It currently works, but I am still facing the constant refreshes that others have reported here 🥲
I ended up using the client side react sdk from auth kit works much better if u don’t need SSR actually
no refreshes anymore but have to use devMode=true in production cause i dont have a custom domain setup
I am also still facing this problem. Its annoying... Until its fixed I think I have to live with it. It dev I have 5 Minutes jwt token validation and in prod 5h. So its not as annoying in prod
Hi guys, what is the state of workos + convex combo? Based on what I have read, seems like the "native" integration is not as ideal yet, only the webhook method is working?
No updates lately unless I missed something somewhere outside of discord
I think @ballingt would know best for that 🙂
I just checked https://github.com/workos/template-convex-nextjs-authkit/commits/main/
Looks like progress is being made and there was an update to workos package to prevent the loading issue.
For me there is still the issue with the token refresh (default at 5 min for workos) - during witch a wrong token seems to be sent and then the identity is not set on the server. Any working twoards solving this?
Hey all, WorkOS is now mentioned in the Convex auth docs with a guide https://docs.convex.dev/auth/authkit
Lets goo! Thanks @ballingt
@wodka would love to hear if this is still an issue with this guide, or if that's been fixed in https://www.npmjs.com/package/@convex-dev/workos
npm
@convex-dev/workos
Helper for authenticating a Convex client via WorkOS AuthKit. Latest version: 0.0.1, last published: a day ago. Start using @convex-dev/workos in your project by running
npm i @convex-dev/workos
. There are no other projects in the npm registry using @convex-dev/workos.Is there a simple explanation for why we need both providers in auth config? Seems only difference is the issuer url?
Pinging some folks this is relevant to @Para @Noss @sonandmjy @mwoof @Niklas @sbkl @Rishad B @adam @Robert Kirk @Bruce @gizmo_jake_04110 @ahmed @Abhishek @John
I can add something, the explanation is that older WorkOS accounts have https://api.workos.com/ as the issuer, but newer ones have https://api.workos.com/user_management/${clientId}`
oh shoot one of these is wrong, let me fix
Thanks a lot for the update! I will rework my current implementation
should be
issuer: "https://api.workos.com/"
for the first one, no client_id at the endthis is awesome! cc: @amianthus
fix coming in 5 min
thanks, my current impl in react is working ok but i will test this one out as well soon!
thank you for your work @ballingt
OK fixed, this should be accurate now
And thanks @nicknisi for doing all the work here!
Is anyone else seeing this issue? (see link)
I’m the only one who has seemed to report it, but I’m fairly confident all my code is correct.
https://github.com/workos/template-convex-nextjs-authkit/commit/31ec6dcb25e0191a6b7e6522f2bfe3295d141be5#r162823020
I don't see the issue when I clone https://github.com/workos/template-convex-react-vite-authkit
@ballingt you dog! This is awesome. Implementing now
@ballingt awesome work!
My backend is nuxt. Let me try to see if it works on nuxt
there's a minor issue I'm looking into around refreshing, but I haven't been able to repro the issue you see yet @Perfect with Vite. I'll try Next.js soon, need to get to some other things first. To me knowledge this works, would love to hear if that's other people's experience too.
Interesting, maybe a nextjs specific issue
I'm sure we can fix it, we'll get there
Are you using nextjs?
The issue was a flash while refreshing a session. The new issue I brought up (yet to be reported by anyone else which makes me suspiscious of it) is unauth showing briefly even if authed.
https://github.com/workos/template-convex-nextjs-authkit/commit/31ec6dcb25e0191a6b7e6522f2bfe3295d141be5#r162823020
Unauth showing briefly even if authed is what I've been getting all along. I don't think it's new.
I get it like this:
And yes, I've done the same logging you did. I'm pretty sure this has come up in the thread already.
I dont get any thrown errors, just the convex auth hook returning the wrong values briefly
Not sure, but Tom was looking into it. My ui looks pretty clunky on page load but gonna ignore it until things are fixed.
sadly still the same problem - for a brief moment it is always not returning a identity on the backend (even though user is authenticated all the time)
Are you also using Next.js?
looking into Next.js today, I couldn't repro for a Vite SPA on Friday
@wodka or @Perfect do you have a repro you could share, or could you tell me more about what you're seeing? I haven't been able to repro this yet.
Here's what I see with my JWT expiration set to 1 minute (and a bit cut out in the middle): when a query reruns and hits an auth error, a new WebSocket is opened and new auth sent without any flash
I'm using https://github.com/workos/template-convex-nextjs-authkit plus a new query that checks auth and throws when it's not set
There are things to fix here like that flash on initial load; that's no good
and I'd rather avoid the websocket reconnects, it would be better to proactively get updated auth tokens
but as a fallback, a query or mutation encounterying expired auth is supposed to work like above
That’s the only issue I’ve been seeing myself - I haven’t had any issue with the refresh flash, that was fixed after the official updates
I didn’t even notice it was doing that, what’s causing that?
that's just how the protocol deals with expired auth, the server cuts the connection and the client is responsible for getting some an updated auth token, and isn't supposed to reconnect until it gets one; I'm showing it here to convey I am hitting this what I would assume woudl cause this issue
Cool, ok I'm going to work on that initial flash first
Ah ok gotcha
And last thing I noticed
but we should be able to fix the slight delay caused by expired auth too, will need to check in with @nicknisi about the client getting pushed an updated token, but it's lower pri since the backup mechanism seems to be working
What controls the Ui in your header auth?
Ok yeah I just wanted to make sure this wasn’t convex
Makes sense why it doesn’t update
Care to elaborate what you mean on this?
Appreciate it! Curious how you will fix 🙂
Each time the server finds auth is expired, it closes the WebSocket connection as a signal to the client to get its act together and get a refreshed token. The WebSocket closes and the client requests fresh auth from WorkOS (which already has it, so this part if instant) and so the websocket is opened again and the auth is sent. This causes a delay vs just getting the updated query. It's lower priority because it's just ones every five minutes, the first interaction (mutation send or new query run or query update receive) will have an extra ~100-300ms delay.
It's not good, but lower pri that the flash
Here's what I see now
1. page is white (or if we were navigating form another page, would show the old page. This is normal Next.js stuff. "Auth is loading" because it's loading during SSR (we say it's always loading during SSR) and then on the client it's also considered loading as WorkOS doesn't know whether we're logged in or not.
2. WorkOS says we're logged out (?)
3. WorkOS says we're logged in, and the convex query data is now loading
4. convex query data loads
The part I'm going to try to fix is eliminating Step 2 (seems like that's wrong, WorkOS should know that we're logged in, or else should keep saying "loading") which might not speed things up, but should make the flash less dramatic.
The next thing I want to fix is having an auth token on initial render: if we're SSRing, we should be able to have a token ready
The final thing I want to fix is making SSR'd data just snap in. With Next.js this required using
preloadQuery
and usePreloadedQuery
, so that's not an automatic speedup for everyone but everything else above could beI don't think the template does any explicit SSR if that is what you were asking. But I agree with all of this.
In my head I think of it working properly like this... (prob have some misunderstaning somewhere though)
1. The nextjs page is loading up. Nothing weird here.
2. workOS useAuth hook is loading, accessToken is being fetched.
3. Things that rely on workOS useAuth can display.
4. Things that rely on convex have to wait for both hooks to finish loading, then gets the token and is happy.
5. Workos refresh happens in background and convex has to get new token with disconnect like you explained once every 5 minutes.
Also when you say "the server finds auth is expired" that would be via the next convex function that checks it right? It would fail because auth is expired? or realtime it knows you mean?
ok first one in https://github.com/workos/template-convex-nextjs-authkit/pull/2
GitHub
Consider no auth token a loading state by thomasballinger · Pull R...
An auth token not yet being available shoudl be considered a loading state for the Convex useAuth helper
Shouldnt tokenLoading be true until the access token exists? At least I assume that naturally
I did the same fix but assumed it was just like a duct tape kinda thing
Yep, that would be the next convex function that checks it. A query has to run (either because a client requests it or the arguments it as called with changed or the database ranges it reads are updated) or a mutation or action has to run to find out that auth is expired. It's considered an edge case, ideally clients should keep sending in fresh auth.
So if I am authed all good
then its been 5 minutes
A query runs, it will fail? -> auth will be resent like it was initially, all good again for another 5
ah yeah you would think so! But apparently not

Ok yeah I found out same thing
https://github.com/workos/template-convex-nextjs-authkit/commit/31ec6dcb25e0191a6b7e6522f2bfe3295d141be5#r162823020
Ok cool I feel like Im not going crazy anymore
yep that's the current state, would like to fix that but it'll take some coordination from the AuthKit side and Convex. I raised this last week at https://github.com/get-convex/convex-js/pull/55#discussion_r2246638584
Since WorkOS wants to manage refreshing their own auth tokens (this is great, they're in a better position to do it than we are) we might need them to push those tokens to the convex client.
Ahh i see
The reasoning being in this comment
But I dont really get why it would be an infinite loop 🤔
either way, unless I missed it, a deeper explanation of how convex auth works in the docs would be awesome I think for someone trying to understand all the stuff going on to make convex happy. 🙂 thx for ur help
@ballingt Is there any way we can push workos to get a react native/expo repo too?
I don't work there, I don't know! But if you're a paying WorkOS customer I'd bet if you reach out to them and let them know that's something you want you'll hear something
opened an issue about for passing an accessToken through to the client when it's available https://github.com/workos/authkit-nextjs/issues/286
I can try to put a repo together - it is based on nextjs, also for me the socket connection is not disconnected - so it keeps updating but briefly sends a empty auth and then the updated auth - in the helper I could not find why this is empty for a moment but I guess the error is coming from that. (the 2 messages are only a few milliseconds appart)
This might be the issue I was having?
Try adding || !accessToken to your loading boolean in provider
Have the same issue in a React SPA app, getting into permannt redirect loops after the session runs out, is there a current solution there?
that actually fixed it - now there are no more strange logout for a split second!! Working for me!
thx @Perfect and @Tom
cool - loading is broken on that hook so you cant rely on it to mean anything. That just adds a check to make sure its actually there.
Has anyone had better luck with Better-Auth? https://github.com/get-convex/better-auth
I tried to get WorkOS working without the annoying refresh and then it kind of got fixed but now having a looping issue.
So tomorrow I will swap out WorkOS for Better-Auth. I just have not found anyone talking about Bett-Auth 100% working.
So tomorrow I will swap out WorkOS for Better-Auth. I just have not found anyone talking about Bett-Auth 100% working.
GitHub
GitHub - get-convex/better-auth: Convex + Better Auth 🔥
Convex + Better Auth 🔥. Contribute to get-convex/better-auth development by creating an account on GitHub.
It is in alpha, probably will have more issues than with the workos integration.
I dont think tom was seeing this looping issue you are talking about, might want to prepare a minimal example to reproduce it/report it
So you think for now just go with WorkOS?
I genuinely just switched to convex Auth for a bit and am planning to switch to either workos or Kinde soon
https://gist.github.com/SirTenzin/faa33f24cd44a76484cd62677966ab28 someone wrote this guide few days ago
Gist
A guide on setting up Kinde Auth with Convex and TanStack Router
A guide on setting up Kinde Auth with Convex and TanStack Router - Kinde + Convex + TanStack Router.md
I'm using WorkOS for projects but I can never get auth inside functions working without that unauthorized error. Debating switching to Kinde for future projects. Is your use case B2B or B2C?
@Hamburger-Fries yeah I haven't been able to reproduce this issue, which template are you using? See upthread for where we fixed the refresh issue
@Rishad B what template or guide are you using? this should be fixed, WorkOS is working well now
The only flash left is the transition in Next.js from server-rendered content to live-refreshing (authed preloadQuery()) and I've got a fix for that coming, should be out this week.
To anyone having WorkOS issues, please let me know or raise a GitHub issue on https://github.com/workos/template-convex-react-vite-authkit or https://github.com/workos/template-convex-nextjs-authkit; I think everything is working here, if you have the refresh issue note the change here https://github.com/workos/template-convex-nextjs-authkit/pull/2/files
I'll put this in a query:
And even though I'm logged in, I'll get:
What package will be updated?
convex
Can you share your setup or and show the WebSocket messages, is an Authenticate message being sent?
Or run in verbose mode, like new ConvexReactClient(url, { verbose: true })
plan is to have a "requireAuth: true" mode that just holds off requesting any queries until an auth token has been sent, and to make sure preloaded data is used until thenWhere can I see websocket messages? verbose: true isn't doing anything different for me
there should be a lot more console.log messages
Ah that makes sense, haven’t tried with SSR currently. I assume this is fixing the preloaded data just not being used properly for authed queries?
you might be SSRing already if you're using preloadQuery?
Will that require auth mode be for all of convex or per query
Nah nothing with preloads yet but I did plan to use it as my default approach for most things
Is preloaded data that used auth just going into the void or something right now?
It should be rendering, but then it disappears for a moment (that flash)
The flash being the loading state with auth getting all synced up?
Ahhh ok
Awesome stuff
I see "server confirmed auth token is valid [v1]", that makes me this think should be working
@Rishad B is this a race condition, does the mutation sometimes work and sometimes not?
It looks to me like
await ctx.auth.getUserIdentity()
would return somethingSorry used it in a query, not mutation
Does the query work later? Just not at first?
I'm using it via preloadQuery
Doesn't work no matter how much I refresh
Oh preloadQuery runs on the server, you need to pass in a token explicitly,
preloadQuery(api.foo.bar, args, { token: yourJWT })
hmm maybe we should add a lint rule for this, this is another time when I wish we know at the type level that a query/mutation/action expects authapi.foo.bar is still a query though which is where I'm testing it
I'm just calling it from the page using preloadQuery
Reminds me - I think clerk and auth0 have examples of how to get the token on server (in tabs in convex docs). Could add for workos, unless it’s already there now. Haven’t checked
I don't understand yet, so you're running preloadQuery from the browser instead of the server? that's unusua but it shoudl work, but you'll need to pass in the JWT explicitly.
Hmm maybe I'm using preloadQuery wrong
Preload is for getting the data on the server so it’s ready when it reaches the client @Rishad B
Oh I switched to query and it works
When I searched docs, I didn't see a token being specified
I'm using it wrong right now obviously, but I just looked
Yeah good point, I have it in my local verison but haven't made a PR
Next.js Server Rendering | Convex Developer Hub
Implement server-side rendering with Convex in Next.js App Router using preloadQuery, fetchQuery, and server actions for improved performance.
We can add a WorkOS tab here
some maybe-interesting changes https://github.com/workos/template-convex-nextjs-authkit/compare/main...thomasballinger:template-convex-nextjs-authkit:tmp from testing, one I have the auth mode thing in I'll update the official one
Also need to figure out how to make sure this works with Expo. I'm doing manual token storage right now. I've also seen this but it's an SSO example, not magic auth which is what I'm using. Although both providers are in auth.config, so maybe it will work.
React Native Expo – Integrations – WorkOS Docs
Learn how to integrate WorkOS SSO into a React Native Expo app.
Yes please!
I should say Tom can do that haha
I wish I worked at convex, if ur in SF they are hiring 😂
Nevermind the docs are on GitHub
I can prob open a PR later
Nice I'll add it. This repo is open source if you want to do a PR, but seeing there are twice the number of venture-backed for-profit companies involved here than usual I think I can manage 😆
But yeah feel free, would love the contribution! Or save your contributing muscles for fixing bugs 😃
Thanks Tom! I will add the new implementation layer and scheme changes today. I spent the day removing Permify and replacing it with Zenstack for ReBAC/RBAC/ABAC
Are you using those for RBAC alongside Convex?
Yes - so far so great. Mostly just RBAC and ReBAC for now.
Cool. I use WorkOS roles and then hardcode permissions though I could use theirs too.
@Perfect check out the experimental
expectAuth: true
option in convex@1.26.0-alpha.9, re the flash conversation we were havingOh sweet, definitely will maybe later tonight
Thanks for letting me know
looking forward to it - for me the errors are now gone in the development, but are now only in production :/
maybe this fixes it as well
Can I use the convex workos docs for react for Tanstack start setup?
did this end up shipping? have been away from laptop for a few days
This is still only in the alpha, new client release tomorrow at this point
saw the release commit for 1.26.0 but cannot get the package anywhere^^
not out yet, testing it; you can do with convex@1.26.0-alpha.12 if you like
Hey @ballingt @nicknisi
Just wanted to see if you had any updates in this area?
Saw your PR Tom, I think everything makes sense in it. The loading state change should not be needed once this PR from Nick is completed right?
https://github.com/workos/authkit-nextjs/pull/297
@ballingt I still get unauthenticated errors on my protected query which is a custom query I use to inject the user to the context. If the user identity doesn't exist, I throw an error. On first load and subsequent load, no problem. But after some time I get the unauthenticated error I throw which causes a client runtime error both in dev and production (using nextjs and vercel). Could this be related to token refresh automatically handled by the workos library? I also use the tanstack query wrapper (@convex-dev/react-query) so I can get pending state. Not sure if related...
Hm I have not thought about auth with the TanStack Query wrapper; how does it handle auth I wonder.
I can confirm the error happens even with the standard useQuery from convex. Just reproduced it.
Ah thanks, that's helpful
Sounds like it's something around re-auth? What is your JWT expiration set to, if you ramp it down to 1 minutes do you see these every 60s?
let me try and feedback
If you've got a repro that makes it easy, the WorkOS team is real responsive so we just need to share the issue
but also it could have to do with the Convex integration part, so I should jump in
So I've put the token to 1 min and using . When I stay in front of my computer and unfocus the tab, I can see the token refreshing and no trouble. I leave my computer for a while and then reopen it, the error is there...
Let me try to make a repro and share it with them.
So the below seemed to solve this. Instead of relying on the accessToken from useAccessToken which is flagged as "might be stale" and the stableAccessToken ref from the workos example in the docs, I replaced it with relying on the getAccessToken which the docs describe as "Get a guaranteed fresh access token. Automatically refreshes if needed. Use this for API calls where token freshness is critical."
I see this:
{
"type": "Authenticate",
"tokenType": "None",
"baseVersion": 1
}
in the sync always when this happens
@Tom At my home page, I have used the
AuthLoading
Authenticated
and Unauthenticated
helpers and now every time I refresh the page, it takes about 2s to load. How can I do better?