N
Nuxt3mo ago
JJ

How to integrate better-auth library with Nuxt 4

I have configured better-auth on my Hono server and am now trying to set up authentication on my frontend that is on a separate port than the server. I currently have the auth-client set up as a plugin:
#plugins/auth-client.ts
import { createAuthClient } from "better-auth/vue";

export default defineNuxtPlugin({
name: "authClient",
parallel: true,
async setup() {
const config = useRuntimeConfig();
const authClient = createAuthClient({
baseURL: config.public.backendUrl,
fetchOptions: {
credentials: "include",
},
});
// Provide it globally
return {
provide: {
authClient,
},
};
},
});
#plugins/auth-client.ts
import { createAuthClient } from "better-auth/vue";

export default defineNuxtPlugin({
name: "authClient",
parallel: true,
async setup() {
const config = useRuntimeConfig();
const authClient = createAuthClient({
baseURL: config.public.backendUrl,
fetchOptions: {
credentials: "include",
},
});
// Provide it globally
return {
provide: {
authClient,
},
};
},
});
And I can succesfully use it in my login page. Alongisde the auth-client I also set up a middleware to protect the users from accessing /dashboard routes when they are not signed in. The middleware currently looks like this:
# middleware/auth.global.ts
// Guard dashboard routes from invalid sessions
export default defineNuxtRouteMiddleware(async (to) => {
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);
if (!session.value) {
if (to.path === "/dashboard") {
return navigateTo("/auth/login");
}
}
});
# middleware/auth.global.ts
// Guard dashboard routes from invalid sessions
export default defineNuxtRouteMiddleware(async (to) => {
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);
if (!session.value) {
if (to.path === "/dashboard") {
return navigateTo("/auth/login");
}
}
});
No in my login page when the customer submits the login form then I send out
const { $authClient } = useNuxtApp();
$authClient.signIn.email
const { $authClient } = useNuxtApp();
$authClient.signIn.email
and navigate the user to /dashboard on success The problem I am running into is that when the user succesfully submit the form I am redirected back to /auth/login for some reason. The user never reaches /dashboard
15 Replies
kapa.ai
kapa.ai3mo ago
Hi @JJ. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on your description and the provided code, the issue likely stems from how your middleware checks the session and handles navigation. In Nuxt, middleware runs both on the server and client, and improper handling of asynchronous session checks or navigation can cause unexpected redirects, such as being sent back to /auth/login even after a successful login. Key points to consider: 1. Session Fetching in Middleware:
Your middleware uses await $authClient.useSession(useFetch). If the session is not immediately available (e.g., due to async cookie propagation or fetch timing), the middleware may not detect the session in time and redirect to /auth/login even though the user just logged in. 2. Redirection Logic:
Your middleware only checks for /dashboard, but if the user is navigating to a nested dashboard route (e.g., /dashboard/settings), the check will not trigger, and the user might get stuck in a redirect loop or be denied access incorrectly. 3. Middleware Execution Timing:
Middleware runs on both server and client. If the session cookie is set on the client after login, but the middleware runs on the server before the cookie is available, the session will appear missing. Recommendations: - Check for session on all dashboard routes:
Use to.path.startsWith('/dashboard') to protect all dashboard-related routes. - Avoid async issues:
If your session is set via a cookie, ensure the cookie is available before the middleware runs. Sometimes, a page reload after login is necessary to ensure the session is available on both server and client. - Return nothing if authenticated:
Only return a redirect if the user is not authenticated. If authenticated, simply return nothing to allow navigation to proceed. Example middleware adjustment:
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async (to) => {
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);

// Protect all /dashboard routes
if (to.path.startsWith('/dashboard') && !session.value) {
return navigateTo('/auth/login');
}
// If authenticated, do nothing (allow navigation)
});
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async (to) => {
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);

// Protect all /dashboard routes
if (to.path.startsWith('/dashboard') && !session.value) {
return navigateTo('/auth/login');
}
// If authenticated, do nothing (allow navigation)
});
Additional tips: - If you still experience issues, try forcing a page reload after login to ensure the session cookie is available to the server-rendered middleware. - Make sure your login logic waits for the session to be established before navigating to /dashboard. References: - Protect App Routes (Nuxt Auth Utils) - When Middleware Runs - Middleware Usage and Return Values If you continue to have issues, please check if the session is correctly set and available in the middleware context after login, and consider adding debug logs to trace the session value during navigation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@448498136198021134> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
JJ
JJOP3mo ago
@kapa.ai The cookie seems to be set correctly but I think it is not yet available on the server side. How can I assure that the middleware checks the cookies at the correct time when they are actually available?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're correct that, in Nuxt, cookies set on the client (e.g., after a login request) are not immediately available to server-side middleware until the next full HTTP request. This is because server-side middleware only has access to cookies sent with the incoming request headers, and cookies set via JavaScript or in a response are not included until the browser makes a new request with those cookies attached. Key points from the knowledge sources: - When you set a cookie (e.g., after login), it is only sent to the server on subsequent requests, not immediately available in the current server-side context. This is a standard browser behavior and not specific to Nuxt.
- The Nuxt documentation and data fetching guide both confirm that cookies are only available to the server after a new request is made with those cookies in the headers. - This is also discussed in a Nuxt GitHub issue and PR, where it is noted that useCookie gets its value from the request headers, which are only updated on the next request. What can you do? - Force a full page reload after login:
After a successful login and cookie set, trigger a window.location.href = '/dashboard' instead of a Nuxt navigation. This ensures a new HTTP request is made, and the cookie is sent to the server, making it available to server-side middleware. - Alternatively, handle the first redirect on the client:
You can skip the server-side middleware check for the initial navigation after login (e.g., by checking import.meta.client in your middleware and returning early), then rely on the middleware for subsequent navigations. Example from the docs:
When we call $fetch in the browser, user headers like cookie will be directly sent to the API.
However, during Server-Side Rendering, due to security risks such as SSRF or Authentication Misuse, the $fetch wouldn't include the user's browser cookies, nor pass on cookies from the fetch response.
...
If you need to forward headers and cookies on the server, you must manually pass them.
Docs: Passing Headers and Cookies
And from a related GitHub PR:
because useCookie get value from event.node.req.headers.cookie, this value not set until next request
GitHub PR #21940
Summary:
You cannot make a cookie available to server-side middleware until the next request. The standard solution is to trigger a full page reload after login, so the cookie is sent to the server and available for authentication checks in middleware. If you need to avoid a full reload, you must handle the first navigation after login on the client only, and rely on server-side checks for all subsequent navigations. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@448498136198021134> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
JJ
JJOP3mo ago
@kapa.ai is
const { data: session } = await $authClient.useSession(useFetch);
const { data: session } = await $authClient.useSession(useFetch);
The correct way to fetch a session in a middleware
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the provided knowledge sources, there is no explicit documentation or example for using
const { data: session } = await $authClient.useSession(useFetch);
const { data: session } = await $authClient.useSession(useFetch);
to fetch a session in Nuxt middleware. The official Nuxt authentication recipes and middleware examples use composables like useUserSession() from nuxt-auth-utils to access session or authentication state in middleware, but do not show usage of a custom $authClient or a .useSession method in this context Protect App Routes. Therefore, the knowledge sources do not contain enough information to confirm whether this is the correct or recommended way to fetch a session in middleware. If $authClient.useSession is a method provided by the better-auth library and it works as expected in your setup, it may be valid, but this approach is not covered in the official Nuxt documentation or examples provided above. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@448498136198021134> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
JJ
JJOP3mo ago
@kapa.ai When calling my betterauth client from my login page the signin function correctly returns user data and I should be directed to /dashboard but the middleware redirects me abck to login for some reason
# Sign in method in LoginForm.vue
await $authClient.signIn.email(
{
email,
password,
callbackURL: "/dashboard",
rememberMe: remember ?? false,
},
{
// Optional: success/failure callbacks
onSuccess: () => {
loading.value = false;
emit("login-success");
},
onError: (err) => {
loading.value = false;
loginError.value = err?.error.message ?? "Invalid email or password";
emit("login-error", err);
},
}
);
# Sign in method in LoginForm.vue
await $authClient.signIn.email(
{
email,
password,
callbackURL: "/dashboard",
rememberMe: remember ?? false,
},
{
// Optional: success/failure callbacks
onSuccess: () => {
loading.value = false;
emit("login-success");
},
onError: (err) => {
loading.value = false;
loginError.value = err?.error.message ?? "Invalid email or password";
emit("login-error", err);
},
}
);
Auth Middleware
export default defineNuxtRouteMiddleware(async (to, from) => {
if (import.meta.server && from.path.startsWith("/auth")) {
console.log("Skipped server side middleware check");
return;
}
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);
console.log(session);
if (!session.value && to.path.startsWith("/dashboard")) {
return navigateTo("/auth/login");
}
});
Auth Middleware
export default defineNuxtRouteMiddleware(async (to, from) => {
if (import.meta.server && from.path.startsWith("/auth")) {
console.log("Skipped server side middleware check");
return;
}
const { $authClient } = useNuxtApp();
const { data: session } = await $authClient.useSession(useFetch);
console.log(session);
if (!session.value && to.path.startsWith("/dashboard")) {
return navigateTo("/auth/login");
}
});
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Your issue—being redirected back to /auth/login after a successful login—is a common one when dealing with authentication in Nuxt apps where the session is stored in a cookie. The root cause is that, after logging in, the session cookie is not immediately available to server-side middleware until the next full HTTP request. This means that when you navigate to /dashboard right after login (using client-side navigation), the server-side middleware does not see the new cookie and thinks you are not authenticated, so it redirects you back to the login page. Key points from the knowledge sources: - Cookies set on the client are only available to the server on the next request. This is a standard browser behavior and is discussed in the Nuxt docs and related issues. After login, a client-side navigation does not trigger a new HTTP request, so the server does not see the updated cookie yet.
- A common solution is to force a full page reload after login so that the cookie is sent to the server and the session is available to middleware.
- Your middleware logic is otherwise correct in checking the session and redirecting if not authenticated, but the timing of when the cookie is available is the problem Protect App Routes. What you can do: - After a successful login, instead of using Nuxt's navigateTo, use a full page reload:
window.location.href = "/dashboard";

window.location.href = "/dashboard";

This will ensure the browser sends the new cookie to the server, and your middleware will see the authenticated session. - Alternatively, you can skip the server-side middleware check for the first navigation after login (as you are already attempting with your import.meta.server && from.path.startsWith("/auth") check), but this is less robust than a full reload. Summary:
Your middleware is running before the session cookie is available to the server. To fix this, trigger a full page reload after login so the cookie is sent to the server and the session is recognized by your middleware. This is a standard approach for cookie-based authentication in Nuxt and is supported by the official documentation and community discussions Protect App Routes. If you need more advanced handling (such as SPA-only navigation), you would need to ensure the session is available on the client and possibly skip server-side checks for the first navigation after login, but this is not covered in the official Nuxt documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@448498136198021134> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
JJ
JJOP3mo ago
This is still unsolved, as the useSession(useFetch) method seems to return null
Mateleo
Mateleo3mo ago
@JJ There is a regression right now with better-auth, 1.3.9 is the last version working
Mateleo
Mateleo3mo ago
GitHub
Nuxt SSR: useFetch integration causes session to return undefined a...
Is this suited for github? Yes, this is suited for github To Reproduce 1、Use Nuxt SSR with better-auth 2、Use const { data: session } = await authClient.useSession(useFetch) 3、Login with GitHub OAut...
JJ
JJOP3mo ago
Thank you so much for referring this to me @Mateleo I ended up building a nuxt composable that can fetch and store session data and I call it through a nuxt plugin and also inside my middleware which protects certain routes. Took some inspiration from this repo https://github.com/atinux/nuxthub-better-auth/blob/main/app/composables/auth.ts But it uses getSession() instead of useSession()
GitHub
nuxthub-better-auth/app/composables/auth.ts at main · atinux/nuxth...
A demo of using Better Auth with Nuxt & NuxtHub (Cloudflare D1 & KV). - atinux/nuxthub-better-auth

Did you find this page helpful?