#auth route

22 messages · Page 1 of 1 (latest)

south pilot
#

how can i sign out a user with convex auth and google oauth provider on a nextjs route handler

whole thornBOT
#

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

#

You can sign users out with Convex Auth using the client-side signOut helper, and you generally don’t need a custom Next.js Route Handler for this.

1. Configure Convex Auth with Google

In convex/auth.ts:

import Google from "@auth/core/providers/google";
import { convexAuth } from "@convex-dev/auth/server";
 
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [Google],
});

This exposes a signOut Convex Action that the Convex Auth client uses internally.[Google config; convexAuth server]

2. Sign out from the client (recommended)

Use the React helper useAuthActions and call signOut from your UI:

"use client";

import { useAuthActions } from "@convex-dev/auth/react";

export function SignOutButton() {
  const { signOut } = useAuthActions();
  return <button onClick={() => void signOut()}>Sign out</button>;
}

This will:

3. Using a Next.js Route Handler

The knowledge sources do not describe a dedicated “sign out via Route Handler” API for Convex Auth. They only show:

  • How to access auth state on the server (convexAuthNextjsToken, isAuthenticatedNextjs) for server components / route handlers[Nextjs server; Nextjs authz]
  • How to sign out from the client as above.
#

If you need a Route Handler endpoint like app/api/signout/route.ts that triggers sign-out, you would still have to call the Convex signOut action from there—but the exact code for doing that is not shown in the provided docs, so I can’t give a concrete, documented snippet for that path.

Given the available information, the supported and documented way is:

  • Configure Convex Auth with Google in convex/auth.ts
  • Call signOut from the client via useAuthActions as shown above.

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

#

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

south pilot
#

@whole thorn when i use the signIn function with "google" provider, i get redirected, but i get the error "No matching routes found"

whole thornBOT
#

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

#

That error usually means the Convex Auth HTTP routes aren’t registered on your Convex deployment, so when Google redirects back to /api/auth/callback/google Convex doesn’t know how to handle it and you see “No matching routes found”.[OAuth setup]

From the docs, check these points:

  1. convex/http.ts must register auth routes

For Convex Auth you need to wire it into the HTTP router:

// convex/http.ts
import { httpRouter } from "convex/server";
import { auth } from "./auth";

const http = httpRouter();

auth.addHttpRoutes(http);

export default http;

If auth.addHttpRoutes(http); is missing, the /api/auth/... routes won’t exist and you’ll get “No matching routes found”.[Manual setup]

  1. Run npx convex dev after changing http.ts

After adding the routes, run:

npx convex dev

so the updated HTTP routes are deployed. If npx convex dev isn’t running or failed, the new routes won’t be active and you’ll still see the error.[Monorepo 404 thread]

  1. Callback URL must match Convex HTTP Actions URL

In your Google OAuth app, the redirect URI must be:

<https://<your-deployment>.convex.site/api/auth/callback/google>

where the origin is your HTTP Actions URL (the .site URL from the Convex dashboard), not the .cloud URL or localhost.[Google instructions; General OAuth]

If all three are correct and you still see “No matching routes found”, the knowledge sources don’t provide further debugging steps beyond verifying that:

#
  • auth.addHttpRoutes(http) is present,
  • npx convex dev is running without errors,
  • and the callback URL exactly matches the .site URL plus /api/auth/callback/google.

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

#

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

south pilot
#

@whole thorn when i finish the sign in process with googles auth provider and i get redirected to the page that i set in the signIn function, the middleware still says false to that console.log(await convexAuth.isAuthenticated());

whole thornBOT
#

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

#

This is a known timing/race issue: right after the OAuth redirect finishes, Convex Auth has issued a token to the browser, but the Convex client/WebSocket hasn’t finished sending that JWT to your Convex deployment yet. During that short window, convexAuth.isAuthenticated() can still return false in middleware or on the server, even though the OAuth flow just succeeded.[Discord race thread]

From the support thread, the recommended approach is:

  • Don’t rely on “immediately after redirect” to be authenticated.
  • Either:
    1. Wait until Convex is actually authenticated on the client, using the React helpers:
      import { useConvexAuth } from "convex/react";
      
      const { isLoading, isAuthenticated } = useConvexAuth();
      
      Only treat the user as logged in when !isLoading && isAuthenticated is true.[Discord race thread]
    2. Or protect against the race in your protected pages/middleware by allowing a brief “loading” state instead of immediately redirecting away when isAuthenticated is false but the auth state is still settling.[Discord race thread]

In other words, your middleware logic should not be:

if (!(await convexAuth.isAuthenticated())) {
  redirect("/login");
}

without any allowance for this transition. Instead, you can:

  • Let the user land on the target page,
  • Use the client-side auth state (useConvexAuth or <Authenticated> / <Unauthenticated>) to decide what to show,
  • Or delay redirecting until the Convex auth state has caught up.
#

The knowledge sources don’t show a fully coded “wait loop” or promise around isAuthenticated, but they explicitly call out that this race exists and that you should “wait until the user is authenticated with Convex to do anything” and base that on useConvexAuth / <Authenticated> rather than the instant after redirect.[Discord race thread]

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

#

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

south pilot
#

@whole thorn when i complete the google sign in process even in client components with <Authenticate>, <AuthLoading>, <Unauthenticated> im never authenticated

whole thornBOT
#

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

#

It sounds like Convex is never seeing a valid JWT from Google, so Convex’s auth state never flips to “authenticated”, even though Google’s own useAuth() (or the OAuth flow) says you’re signed in. There are a few common causes of exactly this behavior in the docs and Discord threads.

Below are the things to check, all based on the provided sources.


1. Make sure Convex is actually wired to your auth provider

For Convex to recognize the Google JWT, you must:

  1. Configure the provider in convex/auth.config.ts (or auth.config.ts) with the correct issuer / JWKS.
  2. Register the auth HTTP routes in convex/http.ts.

If auth.addHttpRoutes(http) is missing, the /api/auth/... routes don’t exist and Convex never gets a token, so <Authenticated> will always render as unauthenticated. [Manual setup]

// convex/http.ts
import { httpRouter } from "convex/server";
import { auth } from "./auth";

const http = httpRouter();

auth.addHttpRoutes(http);

export default http;

After changing this, restart npx convex dev.


2. Verify your auth provider config (Google / AuthKit / custom JWT)

If you’re using WorkOS AuthKit or another custom JWT provider, a misconfigured auth.config.ts can cause Convex to reject the token silently, so useConvexAuth() / <Authenticated> never see an authenticated user.

Example from a WorkOS thread: the second provider entry had an extra applicationID and that prevented Convex from treating the token as valid; <Unauthenticated> always rendered. Removing that applicationID fixed it. [WorkOS thread]

So:

#
  • Double‑check convex/auth.config.ts matches the guide for your provider.
  • If you have multiple customJwt entries, ensure the issuer, jwks, and applicationID fields are exactly what the docs specify for each, and don’t add extra applicationIDs.

If your config is off, Convex will never mark the session as authenticated, even though the external provider says you’re logged in.


3. Don’t use the provider’s useAuth() to gate Convex UI

With WorkOS AuthKit (and similarly with other providers), the docs stress:

  • Use Convex’s useConvexAuth() and <Authenticated>/<Unauthenticated>/<AuthLoading> to decide what to show.
  • Do not rely on the provider’s useAuth() for Convex auth state. [AuthKit React; AuthKit Next.js]

Example from the docs:

import { Authenticated, Unauthenticated } from "convex/react";
import { useAuth } from "@workos-inc/authkit-react";

export default function App() {
  const { user, signIn, signOut } = useAuth();

  return (
    <div>
      {/* use WorkOS for buttons, but Convex for gating */}
      <button onClick={() => (user ? signOut() : void signIn())}>
        {user ? "Sign out" : "Sign in"}
      </button>

      <Authenticated>
        <Content /> {/* Convex is guaranteed authenticated here */}
      </Authenticated>
      <Unauthenticated>
        <p>Please sign in to view data</p>
      </Unauthenticated>
    </div>
  );
}

If you’re only checking the provider’s user and not Convex’s auth state, it will look like “I’m logged in but <Authenticated> never shows”, because Convex is still unauthenticated.


4. Ensure your Convex provider is wired to the auth library correctly

For Next.js + WorkOS AuthKit, the Convex docs show a specific ConvexClientProvider that:

#
  • Wraps everything in the AuthKit provider.
  • Passes a useAuth implementation to ConvexProviderWithAuth that returns isLoading, isAuthenticated, and a fetchAccessToken that yields the access token Convex should validate. [AuthKit Next.js]
"use client";

import { ReactNode, useCallback, useRef } from "react";
import { ConvexReactClient, ConvexProviderWithAuth } from "convex/react";
import {
  AuthKitProvider,
  useAuth,
  useAccessToken,
} from "@workos-inc/authkit-nextjs/components";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <AuthKitProvider>
      <ConvexProviderWithAuth client={convex} useAuth={useAuthFromAuthKit}>
        {children}
      </ConvexProviderWithAuth>
    </AuthKitProvider>
  );
}

function useAuthFromAuthKit() {
  const { user, loading: isLoading } = useAuth();
  const { accessToken, loading: tokenLoading, error: tokenError } =
    useAccessToken();

  const loading = (isLoading ?? false) || (tokenLoading ?? false);
  const authenticated = !!user && !!accessToken && !loading;

  const stableAccessToken = useRef<string | null>(null);
  if (accessToken && !tokenError) {
    stableAccessToken.current = accessToken;
  }

  const fetchAccessToken = useCallback(async () => {
    if (stableAccessToken.current && !tokenError) {
      return stableAccessToken.current;
    }
    return null;
  }, [tokenError]);

  return {
    isLoading: loading,
    isAuthenticated: authenticated,
    fetchAccessToken,
  };
}

If your ConvexProviderWithAuth is not wired like this (e.g. wrong useAuth, not returning the access token, or not wrapped in the provider), Convex will never receive a valid token and <Authenticated> will never activate.


#

5. Be aware of the short race after sign‑in

Even with everything configured correctly, there is a short window right after sign‑in where:

  • The external provider has finished and redirected.
  • But Convex hasn’t yet validated the JWT over the WebSocket.

In that window, useConvexAuth().isAuthenticated can still be false. This is the race condition discussed in the “redirecting to a protected route” thread. [Race condition thread]

To avoid this:

  • Use <AuthLoading> and useConvexAuth().isLoading to show a loading state instead of immediately treating false as “definitely unauthenticated”.
  • Only treat the user as logged out when !isLoading && !isAuthenticated.

Example pattern from the docs: [Auth best practices]

"use client";

import { useConvexAuth } from "convex/react";

export const ClientComponent = () => {
  const { isLoading, isAuthenticated } = useConvexAuth();

  if (isLoading) return <div>Loading...</div>;
  if (!isAuthenticated) return <div>Not allowed!</div>;

  return <div>Welcome, user!</div>;
};

If you redirect away or show <Unauthenticated> while isLoading is still true, you’ll see “never authenticated” behavior even though it would flip to true a moment later.


6. If it’s still always unauthenticated

Given the sources, the remaining likely causes are:

  • Misconfigured auth.config.ts (issuer / JWKS / applicationID).
  • Missing auth.addHttpRoutes(http) in convex/http.ts.
  • ConvexProviderWithAuth not wired to your auth library correctly (wrong useAuth or not returning the access token).
  • Using the provider’s useAuth() instead of Convex’s useConvexAuth() / <Authenticated> to gate Convex data.
#

If you can share:

  • Your convex/auth.config.ts
  • Your convex/http.ts
  • Your ConvexClientProvider / ConvexProviderWithAuth setup
  • And the component where you use <Authenticated>/<Unauthenticated>/<AuthLoading>

I can map them directly against the patterns in the docs and the Discord threads above and point out exactly where Convex is likely failing to see the token.

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