Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When refresh browser on prerender env(SSG), Authentication status is lost. #551

Closed
artisthong opened this issue Oct 18, 2023 · 27 comments
Closed
Labels
bug A bug that needs to be resolved p3 Minor issue provider-local An issue with the local provider

Comments

@artisthong
Copy link

Environment



client : npm run generate > npx serve [output static directory] -l 3001
node server : npm run build(port 3000) > node index.mjs

Reproduction

  1. Call signIn on Login Page.
        const { signIn, status } = useAuth();
	setUser(<resLoginType>returnData.value.rs);
	// await authLogin();
	const wrapBody = {
		username: loginId.value,
		password: passwd.value,
		
	};
	await signIn(wrapBody, { redirect: false });

	navigateTo('/main', { replace: true });
  1. signin is authorize and then move main page. (Screenshots : Chrome Inspector Application > Cookie and check useAuth status value)
스크린샷 2023-10-18 오후 6 02 46 스크린샷 2023-10-18 오후 6 09 34
  1. After refreshing the browser, useAuth status value is lost and move the login.
스크린샷 2023-10-18 오후 6 18 48

Describe the bug

  1. Authentication passed on the login page.
  2. Refreshed the main page.
  3. Authentication status is lost and moved to the login page.

I guess this is probably a problem that occurs when pre-rendering is performed on the client-driven server side.
Is there a solution to this?

Additional context

"nuxt.config.ts" part the below.

import { resolve } from 'node:path';

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
	devtools: { enabled: true },
	build: {
		transpile: ['vuetify', 'jsonwebtoken'],
	},
	alias: {
		'./runtimeConfig': './runtimeConfig.browser',
		cookie: resolve(__dirname, 'node_modules/cookie'),
	},

	ssr: true,
	runtimeConfig: {
		public: {
			baseApiUrl: process.env.BASE_API_URL,
			midAwsUrl: process.env.MIDDLEWARE_LAMBDA_URL,
			mode: process.env.ENV_MODE,
		},		
	},

	nitro: {
		preset: 'node-server', //'aws-lambda',
		storage: { '.data:auth': { driver: 'fs', base: './.data/auth' } },
		routeRules: {
			'/': { ssr: true, prerender: true },
		},		
	},
	vite: {
		envDir: process.cwd(),
		define: {
			'process.env.DEBUG': false,
			'window.global': {},
		},
		css: {
			preprocessorOptions: {
				scss: {
					additionalData: '@import "@/assets/main.scss"; ',
				},
			},
		},
	},
	modules: [
		'@pinia/nuxt',
		'@pinia-plugin-persistedstate/nuxt',
		'@nuxtjs/i18n',
		'@sidebase/nuxt-auth',
	],
	auth: {
		provider: {
			type: 'local',
			token: {
				signInResponseTokenPointer: '/token/accessToken',
				maxAgeInSeconds: 18000,
				// sameSiteAttribute: 'lax',
			},
		},
		baseURL: 'http://localhost:3000/api/auth', 

		globalAppMiddleware: {
			isEnabled: true,
		},
	},
	i18n: {
		strategy: 'no_prefix',
		locales: [
			{
				code: 'vi',
				name: 'Vietnamese',
				file: 'vi-VN.ts',
			},
			{
				code: 'en',
				name: 'English',
				file: 'en-US.ts',
			},
			{
				code: 'ko',
				name: 'Korean',
				file: 'ko-KR.ts',
			},
		],
		experimental: {
			jsTsFormatResource: true,
		},
		lazy: true,
		langDir: 'lang',
		defaultLocale: 'en',
	},
	experimental: {
		payloadExtraction: false,
	},
	css: ['vuetify/styles', '@/assets/dev.scss'],
	pinia: {
		autoImports: [
			// `defineStore`를 자동으로 임포트함.
			'defineStore', // import { defineStore } from 'pinia'
			['defineStore', 'definePiniaStore'], // import { defineStore as definePiniaStore } from 'pinia'
		],
	},
	app: {
		head: {
			title: 'D-Sales Store',
			// titleTemplate: 'input title template',
			link: [
				{ rel: 'stylesheet', href: '' },
				{ rel: 'preconnect', href: '' },
				{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
				// font - NotoSans 추가
				{
					rel: 'stylesheet',
					type: 'text/css',
					href: 'https://fonts.googleapis.com/css?family=Noto+Sans+KR:400,500,700',
				},
			],
		},
	},
});

Logs

No response

@artisthong artisthong added the bug label Oct 18, 2023
@artisthong artisthong changed the title When refresh browser on prerender env(SSG), Authtification status is lost. When refresh browser on prerender env(SSG), Authentication status is lost. Oct 18, 2023
@TouchSek
Copy link

TouchSek commented Nov 6, 2023

Same issue

@zoey-kaiser
Copy link
Member

Hi @artisthong, so this only happens when running the frontend statically, but not when you build it and run it through the Nuxt Server (AKA only happens when you run SSG and not SSR)?

Also, does the cookie get cleared on the reload of the page or is it kept?

Could you also provide a replication of your current setup? Otherwise it may take a bit longer for me to create a setup on which I can test this!

@TouchSek
Copy link

TouchSek commented Nov 6, 2023

For me it cleared the cookie one we reloaded the page.

@TouchSek
Copy link

TouchSek commented Nov 6, 2023

@zoey-kaiser Make sure you can make nuxt.config.ts

ssr: true,

Or leave it as by default.

@KyleSmith0905
Copy link
Contributor

KyleSmith0905 commented Nov 29, 2023

@TouchSek If you tab in and out of your site does the authentication state fix itself?

Like this:

  1. Navigates to yourwebsite.com
  2. Observe that the unauthenticated state is available to user ("Please sign in")
  3. Clicks away into another tab
  4. Clicks back on the yourwebsite.com tab
  5. Observe that the authentication state is now showing ("Signed in as...")

I might have the same issue, I just wanna know if it's the same thing or not, so I can make my own issue.
Here is the reproducible example I made if you wanna compare:
Site: https://minimal-nuxt-app-git-using-isr-kylesmith0905.vercel.app/
Repo: https://github.com/KyleSmith0905/mre--nuxt-app/tree/using-isr

@KyleSmith0905
Copy link
Contributor

If either of you are having the problem I stated above, it's because Nuxt Auth is incorrectly passes down the authentication info when pages are being cached/prerendered. In dev tools run: console.log(useNuxtApp().payload.state['$sauth:loading']), observe that it logs as false, meaning that Nuxt Auth is not sending a loading state by default. Nuxt Auth also doesn't revalidate client side (nor does it need to).

As a cheap solution you can do this:

onMounted(() => {
	getSession()
})

Since auth.status isn't initially "loaded", you could add a custom loading status. (code untested)

const authLoading = ref(true);

onMounted(() => {
	getSession().then(() => loading.value = false)
})

Or as a better solution, you can probably create a composable (if you do, please share).

I was trying to slowly build a PR to fix (as I need this for a personal project). But I'm spending a lot of my personal time on work-related projects.

A permanent solution might be to:

  1. Add a config option to turn off all server-side Nuxt Auth use (but there isn't fine grained control).
  2. Detect if an option has prerender/isr/swr in routeRules (but there are some variables to this, example: Cloudflare might cache).
  3. Add an option in the routeRules to turn off server-side Nuxt Auth functionality (but a lot of extra lines to the Nuxt config).

@gretchelin
Copy link

I'm also facing this issue using local provider with SSR. The auth state is fine during CSR, but try refreshing the page, or do navigation that is not using history and it's gone.

Here's the reproduction: https://stackblitz.com/edit/nuxt-starter-k4cqjg

  • Access login page and input any email and and password
  • The login will be successful and user will be redirected to protected page (/app)
  • Click any navigation link except Regular Link and the auth state is kept.
  • Click Regular Link, and it will bring user to another unprotected page.
  • Click back to app or login page and the auth state is gone (if clicking app, it'll redirect to login, if clicking login, it will render login page, even though user did not logout)

@simkuns
Copy link

simkuns commented Dec 5, 2023

@gretchelin Your reproduction works fine when preview is opened in new tab.

@gretchelin
Copy link

thanks for mentioning that @simkuns . I only recently remembered to check on this issue, and after testing the repro in new tab, yeah, it weirdly works. Will play around with the repro to see if it's something implementation-wise (as I use mock data in it, so that might be why it behave weirdly).

@medusiora
Copy link

medusiora commented Dec 14, 2023

I tried to check this with nuxt-auth@^0.6.3 with refresh provider(Many thanks for this feature), and have the same problem. I hosted the website on DigitalOcean using App Platform and the API is on Droplet using Symfony 6.3.*

As I see,

  • The token is not set or cleared immediately after login.
  • I also tried to use another API endpoint(another API project) to check login process, it work well (but not for latest project).

So, I not sure if this a good solution or not but I disable SSR and force set token again with useCookie and it work for me.

nuxt.config.ts

ssr: false,

auth: {
  globalAppMiddleware: {
    isEnabled: true,
  },
  baseURL: process.env.NUXT_PUBLIC_API_BASE + '/',
  provider: {
    type: 'refresh',
    endpoints: {
      signIn: { path: '/api/login', method: 'post' },
      signOut: false, // { path: '/api/token/invalidate', method: 'post' },
      signUp: { path: '/api/register', method: 'post' },
      getSession: { path: '/api/users/profile', method: 'get' },
      refresh: { path: '/api/token/refresh', method: 'post' },
    },
    token: {
      signInResponseTokenPointer: '/token',
      maxAgeInSeconds: 3600,
    },
    refreshToken: {
      signInResponseRefreshTokenPointer: '/refreshToken',
      maxAgeInSeconds: 3600 * 24 * 30,
    },
  },
  session: {
    enableRefreshPeriodically: false,
    enableRefreshOnWindowFocus: false,
  },
},

app.vue

const { token } = useAuth()

const jwt = useCookie('auth:token', {
  maxAge: 3600,
  expires: new Date(Date.now() + 3600 * 1000),
  path: '/',
  sameSite: 'lax',
  watch: true,
  default() {
    return token.value?.replace('Bearer ', '')
  },
})

watch(
  () => token.value,
  (value) => {
    jwt.value = value?.replace('Bearer ', '')
  },
  { immediate: true },
)

I hope we find out the better solution soon.

Edit:
here is what I found on frontend log in PROD mode when enable ssr: true

[nuxt] [request error] [unhandled] [500] [POST] "https://domainname.com/api/token/refresh": <no response> fetch failed
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)  
at async $fetch2 (./server/chunks/nitro/node-server.mjs:2754:15)  
at async setup (./server/chunks/app/server.mjs:4384:21)  
at async Object.callAsync (./server/chunks/app/server.mjs:88:16)  
at async applyPlugin (./server/chunks/app/server.mjs:440:35)  
at async applyPlugins (./server/chunks/app/server.mjs:460:7)  
at async createNuxtAppServer (./server/chunks/app/server.mjs:17924:7)  
at async Object.renderToString (./server/node_modules/vue-bundle-renderer/dist/runtime.mjs:173:19)  
at async ./server/chunks/handlers/renderer.mjs:217:21  
at async ./server/chunks/handlers/renderer.mjs:19:22

but I did see any error/access logs in the API server with this request

@angelokeirsebilck
Copy link

thanks for mentioning that @simkuns . I only recently remembered to check on this issue, and after testing the repro in new tab, yeah, it weirdly works. Will play around with the repro to see if it's something implementation-wise (as I use mock data in it, so that might be why it behave weirdly).

Hi, I've got the same problem as you stated above. After doing refresh or new tab etc auth state is gone (cookie is gone). Did you find out what was causing this?

@aroozzak
Copy link

aroozzak commented Feb 1, 2024

try this code

auth: {
    ...
   cookie: {
            options: {
                expires: new Date(new Date().getTime() + 20000000000).getTime(), //thats today + a year
                maxAge: 31622400
            }
        }
   ...
}

@Zhenmao
Copy link

Zhenmao commented Feb 2, 2024

I'm facing the same issue here. When running the frontend statically generate, reloading the page loses the auth status. It only happens when ssr is set to true in the nuxt config. The cookie containing the token is still there.

Here are two minimal examples:

  1. ssr true, reloading redirects to the login page
    Repo: https://github.com/Zhenmao/nuxt-auth-local-bug/tree/ssr-true
    Demo: https://nuxt-auth-local-bug-ssr-true.netlify.app/
  2. ssr false, reloading stays on the home page
    Repo: https://github.com/Zhenmao/nuxt-auth-local-bug/tree/ssr-false
    Demo: https://nuxt-auth-local-bug-ssr-false.netlify.app/

@zoey-kaiser zoey-kaiser added p3 Minor issue bug A bug that needs to be resolved provider-local An issue with the local provider and removed bug labels Feb 23, 2024
@MelvinIdema
Copy link

If either of you are having the problem I stated above, it's because Nuxt Auth is incorrectly passes down the authentication info when pages are being cached/prerendered. In dev tools run: console.log(useNuxtApp().payload.state['$sauth:loading']), observe that it logs as false, meaning that Nuxt Auth is not sending a loading state by default. Nuxt Auth also doesn't revalidate client side (nor does it need to).

As a cheap solution you can do this:

onMounted(() => {
	getSession()
})

Since auth.status isn't initially "loaded", you could add a custom loading status. (code untested)

const authLoading = ref(true);

onMounted(() => {
	getSession().then(() => loading.value = false)
})

Or as a better solution, you can probably create a composable (if you do, please share).

I was trying to slowly build a PR to fix (as I need this for a personal project). But I'm spending a lot of my personal time on work-related projects.

A permanent solution might be to:

  1. Add a config option to turn off all server-side Nuxt Auth use (but there isn't fine grained control).
  2. Detect if an option has prerender/isr/swr in routeRules (but there are some variables to this, example: Cloudflare might cache).
  3. Add an option in the routeRules to turn off server-side Nuxt Auth functionality (but a lot of extra lines to the Nuxt config).

Thanks to you I regained my sanity. Having an authenticating flash before loading the content on first load is an ugly solution but at least it's working now. Thank you. Did you ever got the time to come up with a better solution?

@KyleSmith0905
Copy link
Contributor

Thanks to you I regained my sanity. Having an authenticating flash before loading the content on first load is an ugly solution but at least it's working now. Thank you. Did you ever got the time to come up with a better solution?

I created a pull request (#610) to fix that issue. They're currently reviewing it. On the project I was having the issue, I just installed the fork I created, instead of using @sidebase/nuxt-auth. I deployed it to NPM, but I'm not sharing the package name since I probably won't keep it updated, lol.

@Vijayabhaskar96
Copy link
Contributor

#712 This PR fixes the data getting lost issue, but you still need to set the prerender:false for middleware protected routes in routeRules so that client-side middleware is forced to run and load the states.

@kingychiu
Copy link

I am working on a new nuxt project. I am seeing the same issue with

  • ssr: true
  • local provider
  • running with npm run dev

@RMFogarty
Copy link

For anyone also facing this, KyleSmith fix did not fully work for me. The hack I came up with is to manually pull the auth token from the cookie before calling getSession():

const { rawToken } = useAuthState()
const authLoading = ref(true);

onMounted(() => {
    // Grab auth token from cookie
    const authToken = useCookie('auth.token');

    // Set raw token from Nuxt Auth
    rawToken.value = authToken.value;

    getSession({ force: true }).then(() => {
        authLoading.value = false
    });
})

@MelvinIdema
Copy link

MelvinIdema commented Apr 30, 2024

For anyone who's only using the local provider of nuxt-auth I highly recommend implementing authentication without a package. It's simple yet effective.

This package is just not good at simple authentication flow with just the local provider. I've lost days trying to work around annoying bugs and over-engineered solutions for a simple Auth flow.

In the end I just build my own solution through a simple global middleware checking for the token in a Pinia store, a Pinia store saving and retrieving the token from a cookie with the useCookie composable and in the app.vue a simple setup script that retrieves whatever user data is necessary. And a login form of course.

However, if you need to implement multiple providers this package is of course amazing and my respect to the maintainers of it!

@ralphcrisostomo
Copy link

While we wait for a proper fix, I have created a Nuxt 3 plugin based on @RMFogarty's suggestion.

For static site generation (SSG), the authentication process, especially the handling of cookies, should primarily occur on the client side because cookies are not accessible at build time and are typically bound to the user's session.

This plugin should work for provider type "refresh" and "local".

// plugins/nuxt-auth-ssg.js
import { defineNuxtPlugin } from '#app';
import { useState } from '#imports';

export default defineNuxtPlugin(nuxtApp => {
    const authLoading = useState('authLoading', () => ref(true));  // Initialize and provide globally

    nuxtApp.hook('app:mounted', async () => {
        const { getSession, refresh } = useAuth();
        const { rawToken, rawRefreshToken } = useAuthState();

        if (process.client) {
            const authToken = useCookie('auth.token');
            const authRefreshToken = useCookie('auth.refresh-token');

            // Only proceed if there is at least an authToken
            if (authToken.value) {
                rawToken.value = authToken.value;
                if (authRefreshToken.value && rawRefreshToken) {
                    rawRefreshToken.value = authRefreshToken.value;
                }

                try {
                    const authMethod = refresh && authRefreshToken.value ? refresh : getSession;
                    await authMethod({ force: true });
                } catch (error) {
                    console.error('Failed to initialize session:', error);
                    // Optionally update state or notify users that an error occurred
                } finally {
                    authLoading.value = false; // Ensure loading is set to false when done
                }
            } else {
                authLoading.value = false; // Set loading false if no auth token
            }
        }
    });
});

Now, you can inject "authLoading" in any component where you need it.

<script setup>
const { status } = useAuth();

const authLoading = useState('authLoading');
</script>

<template>
  <div v-if="authLoading">
    Loading...
  </div>
  <div v-else>
    <!-- Your authenticated or unauthenticated UI here -->
   <div> {{ status }} </div>
  </div>
</template>

@ThorsAngerVaNeT
Copy link

ThorsAngerVaNeT commented May 1, 2024

@ralphcrisostomo tried your solution but auth middleware redirects to login page earlier than plugin executed. Am I doing something wrong?

@ralphcrisostomo
Copy link

@ralphcrisostomo tried your solution but auth middleware redirects to login page earlier than plugin executed. Am I doing something wrong?

Could you share your code so I can understand your issue clearly.

@ThorsAngerVaNeT
Copy link

Could you share your code so I can understand your issue clearly.

I checked it in this reproduction and it still logout
#551 (comment)

@Vijayabhaskar96
Copy link
Contributor

My pull request #712 still hasn't been reviewed yet, meanwhile if anyone is facing the problem you can install my version @vijayabhaskar96/nuxt-auth
It simply stores the session data as cookie and loads it back if available. If you're navigation guarding some routes, disable prerendering for those routes so client side middleware can handle things properly.

@dev-davexoyinbo
Copy link

dev-davexoyinbo commented May 18, 2024

@ralphcrisostomo tried your solution but auth middleware redirects to login page earlier than plugin executed. Am I doing something wrong?

Could you share your code so I can understand your issue clearly.

I had the same issue and wasn't able to fix it, I had to switch to a completely different auth module. You could try out this auth module instead @workmate/nuxt-auth. This works for me

@taharehany
Copy link

Is this issue still unresolved?!!!

Any one has a solution?? @artisthong

@taharehany
Copy link

taharehany commented Jun 8, 2024

After long and painstaking research, I found this:

const headers = useRequestHeaders(['cookie']) as HeadersInit
const { data } = await useFetch('/api/example', { headers })

this will set cookies from the client side to the request you want in the Nuxt server side

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug that needs to be resolved p3 Minor issue provider-local An issue with the local provider
Projects
None yet
Development

No branches or pull requests