#Convex Auth in Production Error

26 messages · Page 1 of 1 (latest)

tropic spear
#

I am trying to get authentication to work with my convex + better auth set up. I've got the following envs in my dev and production deployments in Convex:
BETTER_AUTH_SECRET, SITE_URL. In development, everything works fine however in production I am unable to login or sign up. I am able to create an account but in production it gives me this error:

Error

Uncaught Error: Unauthenticated
    at getAuthUser [as getAuthUser] (../../node_modules/@convex-dev/better-auth/src/client/index.ts:538:13)
    at async handler (../../convex/auth.ts:44:9)

My getCurrentUser method looks like this:

export const getCurrentUser = query({
    args: {},
    handler: async (ctx) => {
        const user = await authComponent.getAuthUser(ctx);
        return {
            ...user,
            userProfiles: await ctx.db
                .query('userProfiles')
                .withIndex('by_userId', (q) => q.eq('userId', user?._id))
                .collect()
        };
    }
});

I would really appreciate some help here I've been trying for hours to resolve this and find out what is causing this because in production it is just not working. It's deployed on vercel

inner mirageBOT
#

Hi @tropic spear. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Your production error is caused by the fact that in production, when there is no authenticated user, calling authComponent.getAuthUser(ctx) throws an error. In development, you may not have noticed this because your authentication state was always valid, but in production, this is a common pitfall after recent updates to Better Auth.

Solution:
You should use authComponent.safeGetAuthUser(ctx) instead of authComponent.getAuthUser(ctx). The safeGetAuthUser method will return null if the user is not authenticated, rather than throwing an error. This change is required after Better Auth v0.8, as documented in the migration guide:

export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const user = await authComponent.safeGetAuthUser(ctx);
    if (!user) {
      return null;
    }
    return {
      ...user,
      userProfiles: await ctx.db
        .query('userProfiles')
        .withIndex('by_userId', (q) => q.eq('userId', user._id))
        .collect()
    };
  }
});

This will prevent the "Unauthenticated" error and allow your function to return null when there is no authenticated user, which is the expected behavior in production as well as development.

Reference:
See the migration note:

authComponent.getAuthUser() now throws an error if the user is not found. Use authComponent.safeGetAuthUser() to match the previous behavior.
Better Auth migration guide

#

Additional notes:

  • Make sure your production environment variables (BETTER_AUTH_SECRET, SITE_URL) are set correctly and match your production deployment URLs.
  • If you continue to have issues, double-check that your SITE_URL matches the actual deployed URL and that all environment variables are present in your Convex production deployment, not just in Vercel.

Let me know if you need more help!

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tropic spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tropic spear
#

@inner mirage should the SITE_URL be the vercel deployed app url

inner mirageBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, in production, the SITE_URL should be set to the URL where your Vercel-deployed frontend app is accessible. This is important because SITE_URL is used for redirecting users back to your site during OAuth sign-in and for magic links sent via email. For example, if your Vercel deployment is at <https://your-app.vercel.app,> then SITE_URL should be set to that URL (without a trailing slash).

This is confirmed in the Convex Auth manual setup documentation:

The SITE_URL environment variable is used when redirecting back to your site during OAuth sign-in and for magic links sent via email.
Manual Setup – Configure SITE_URL

Make sure that in your Convex production deployment, the SITE_URL matches your actual deployed frontend URL on Vercel.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tropic spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tropic spear
#

@inner mirage Aren't those above manula steps for convex auth? I am using better auth

inner mirageBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You're correct—the manual steps I referenced above are for Convex Auth, not Better Auth. For Better Auth with Convex, the setup is slightly different, but the principle for SITE_URL is the same: it should be set to the URL where your frontend is deployed (e.g., your Vercel app URL).

From the Better Auth + Convex guides, for production you should:

  • Set SITE_URL to your deployed frontend URL (e.g., <https://your-app.vercel.app>).
  • Set BETTER_AUTH_SECRET as well.

This is shown in the Better Auth framework guides for React, Next.js, SvelteKit, etc. For example, the React guide says:

Add your site URL to your Convex deployment.

npx convex env set SITE_URL <http://localhost:5173>

(For production, use your deployed URL instead of localhost.)

Add environment variables to the .env.local file created by npx convex dev. It will be picked up by your framework dev server.

SITE_URL=<https://your-app.vercel.app>

React (Vite SPA) Guide

So, for Better Auth, yes, your SITE_URL should be your Vercel deployed app URL in production. This ensures redirects and authentication flows work correctly in your production environment.

If you have further issues, double-check that your SITE_URL matches your actual deployed frontend URL and that all required environment variables are set in your Convex production deployment, not just in Vercel.<|source|>

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tropic spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tropic spear
#

@inner mirage What are the steps I need to do if I change the BETTER_AUTH_SECRET? What do I do with the jwks table

inner mirageBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If you change the BETTER_AUTH_SECRET in your Convex + Better Auth setup, you need to:

  1. Update the environment variable: Set the new BETTER_AUTH_SECRET value in your Convex deployment's environment variables. You can do this via the Convex dashboard or CLI:

    npx convex env set BETTER_AUTH_SECRET <new-secret>
    

    Make sure this is done for all relevant deployments (dev, prod, etc.)<sup>React (Vite SPA) Guide</sup>.

  2. Restart your deployment: After updating the secret, restart your Convex deployment to ensure the new secret is picked up.

  3. Sessions and tokens: Changing the BETTER_AUTH_SECRET will invalidate all existing sessions and tokens, since they were signed/encrypted with the old secret. Users will need to sign in again.

  4. JWKS Table: The knowledge sources do not mention a jwks table or any required action regarding JWKS when changing BETTER_AUTH_SECRET in Better Auth. There is no indication that you need to manually update or clear a JWKS table as part of this process. If you are using custom JWT providers or have a custom JWKS endpoint, ensure that any keys or secrets used for signing tokens are also updated accordingly, but for standard Better Auth usage, this is not required.

Summary:

  • Change the BETTER_AUTH_SECRET in your environment variables.
  • Restart your deployment.
  • No action is needed for a jwks table according to the available Better Auth documentation.
#

If you need to rotate keys for JWT signing (not just the secret), refer to your specific JWT provider's documentation, as this is not covered in the Better Auth guides provided.<sup>React (Vite SPA) Guide</sup>

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tropic spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tropic spear
#

@inner mirage
My middleware.ts file looks like this:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const isAuthRoute = (pathname: string) =>
    ['/'].some((route) => pathname.startsWith(route));
const isProtectedRoute = (pathname: string) =>
    pathname.startsWith('/generate') ||
    pathname.startsWith('/avatars') ||
    pathname.startsWith('/knowledge') ||
    pathname.startsWith('/library') ||
    pathname.startsWith('/stream');

export async function middleware(request: NextRequest) {
    const { pathname } = request.nextUrl;
    const sessionCookie = request.cookies.get('better-auth.session_token');
    const isAuthenticated = !!sessionCookie?.value;

    if (isAuthRoute(pathname) && isAuthenticated) {
        return NextResponse.redirect(new URL('/stream', request.url));
    } else if (isProtectedRoute(pathname) && !isAuthenticated) {
        return NextResponse.redirect(new URL('/', request.url));
    }
    return NextResponse.next();
}

export const config = {
    matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};
inner mirageBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Your current middleware.ts checks for the presence of the better-auth.session_token cookie to determine authentication. However, according to the Convex + Better Auth documentation, this approach is not fully reliable for protecting routes, because the presence of the session cookie does not guarantee that the Convex JWT (used for backend authentication) is valid or present. There can be a race condition where the session cookie exists but the Convex JWT is not yet set, especially right after login, which can lead to unauthorized access or errors<sup>Better Auth Authorization</sup>.

Recommended Approach:

  • Use Convex’s authentication helpers (such as useConvexAuth() on the client, or server-side helpers if available) to check authentication state, rather than relying solely on the cookie.
  • If you want to protect routes in middleware, you should ideally check for the presence of the Convex JWT cookie (usually named better-auth.convex_jwt) or use a server-side helper if Better Auth exposes one for Next.js middleware.

From the Discord support thread and Better Auth docs:

#

What you should do:

  • If you continue using cookie checks, consider checking for both better-auth.session_token and better-auth.convex_jwt cookies for a more robust check.
  • For best practices, use the authentication state provided by Better Auth or Convex in your server components and client components, and ensure your Convex backend functions always check authentication with ctx.auth.getUserIdentity() or similar, as described in the docs<sup>Better Auth Authorization</sup>.

Summary:
Your current middleware is a reasonable first step, but it is not fully robust for production use with Convex + Better Auth. Consider using more reliable authentication state checks as described above, and always enforce authentication in your Convex backend functions.

If you need a middleware example that uses Convex Auth helpers, refer to the Convex Auth Next.js middleware docs, but note that these are for Convex Auth, not Better Auth. The Better Auth docs do not provide a specific middleware example for Next.js, so you may need to adapt your approach as Better Auth evolves.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tropic spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tropic spear
#

Give me middleware code @inner mirage

inner mirageBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

I'm sorry, but the knowledge sources do not provide an official or recommended middleware implementation for Next.js that works specifically with Better Auth and Convex. The Better Auth documentation and support threads show that most authentication state is managed on the client using Convex's React helpers, and that there are known issues with relying solely on cookies in middleware (such as the better-auth.session_token or better-auth.convex_jwt cookies) due to race conditions and timing issues after login or signup. There is no server-side helper or middleware function for Next.js provided in the Better Auth documentation.

If you need robust route protection, the recommended approach is to:

  • Use Convex's authentication state in your client components (e.g., useConvexAuth()).
  • Always enforce authentication in your Convex backend functions using ctx.auth.getUserIdentity() or similar checks.

If you still want to use middleware, you can check for the presence of both better-auth.session_token and better-auth.convex_jwt cookies, but this is not guaranteed to be fully reliable, as discussed in the Discord support threads and documentation.

In summary:
There is no officially recommended or documented middleware code for Better Auth + Convex in Next.js in the provided knowledge sources. If you need a robust solution, rely on client-side checks and backend enforcement as described above. If you want to proceed with a cookie-based approach, be aware of its limitations and potential race conditions.

If you have further questions or need help with a different aspect of Convex or Better Auth, please let me know!

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: