Redirect to previous url after login in nuxt.js - vue.js

I basically want to redirect to the previous url when a user has successfully logged in.
I redirect to the login page with the previous url such as /login?redirect=/page1/page2.
And I want when a user authenticates to be redirected back to that url.
I am using the auth-module here: https://auth.nuxtjs.org/
How I login the user.
methods: {
async submit() {
await this.$auth.loginWith('local', {
data: this.form
})
}
}
The only thing that I could found in the docs is this: https://auth.nuxtjs.org/getting-started/options#redirect
which however only redirects to a specific page instead of the previous page in the query.
Any ideas on how to achieve this?

You Can Do This
this.$router.back()
And its go back to the last route.
Programmatic Navigation | Vue Router
https://router.vuejs.org/guide/essentials/navigation.html
Thanks.

There is a fairly detailed discussion in github about Nuxt having an issue with a redirect when you are hitting a protected page directly. The redirect goes to the default page redirect rather than the previously hit page. The correct behavior should be to store the redirect and then proceed to it after authentication (login) with correct credentials.
3 days ago (Apr 14, 2019), MathiasCiarlo submitted a PR on the auth-module repo to fix this. The base reason why the redirect was "lost" has to do with the state of the redirect value not being allowed to be set as a cookie in SSR mode. His code impacts the storage.js file, in particular the setCookie() method. I've included that changed method here just for reference.
setCookie (key, value, options = {}) {
if (!this.options.cookie) {
return
}
const _key = this.options.cookie.prefix + key
const _options = Object.assign({}, this.options.cookie.options, options)
if (isUnset(value)) {
Cookies.remove(_key, _options)
} else {
// Support server set cookies
if (process.server) {
this.ctx.res.setHeader('Set-Cookie', [_key + '=' + value])
} else {
Cookies.set(_key, value, _options)
}
}
return value
}
I've personally just altered my npm myself, but you could probably fork the repo and use that forked npm for the time being. Or you could wait until the PR is merged into the mainline of the auth-module repo.

Related

How to protect multiple routes from unauthorized access in Next.js using next-auth

I am using Next.js and I have a folder learning inside my pages folder. Now, this learning folder has about 10 pages.
All these pages need to redirect to the index page if the user is not logged in. The following code does the job, but is there any other way to protect multiple pages, so that I don't need to add this same code again and again to all the pages ?
export async function getServerSideProps(context) {
//redirect to index page if not logged in
const session = await unstable_getServerSession(context.req, context.res, authOptions);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false
}
}
}
}
I believe you are confused between protecting pages and protecting API ROUTES.
If you simply want to protect pages, you can indeed use middleware
However, if you wish to protect API Routes (e.g prevent a user from deleting data using your API endpoint and postman), I believe you need to use this unstable_getServerSession
Except creating reusable function, it's true that I didn't find anywhere in the doc how to set it for multiple paths in one folder only...
you can use middleware. docs: https://next-auth.js.org/configuration/nextjs#middleware
Create a middleware.ts (or .js) file at the root or in the src directory (same level as your pages).
If you only want to secure certain pages, export a config object with a matcher:
export { default } from "next-auth/middleware"
// otherwise your app would require authentication for all
export const config = { matcher: ["/dashboard"] }
Now you will still be able to visit every page, but only /dashboard
will require authentication.
If a user is not logged in, the default behavior is to redirect them
to the sign-in page.
that example is from the docs. You can also write a custom middleware
import { NextResponse } from "next/server";
export function middleware(req) {
const sessionCookie = req.cookies.get("session");
}
// you could add more if logic for other pages
if (req.nextUrl.pathname.startsWith("/admin ")) {
if (!sessionCookie) {
return NextResponse.redirect("/home");
}
}

Reevaluate Nuxt.js middleware without a route change

I'm wondering if it's possible to essentially "reevaluate" the middleware conditions without actually changing the current route.
The middleware's purpose is to prevent non-logged-in users from accessing the "dashboard".
My issue is, a user could become logged in or logged out without necessarily changing route but they wouldn't be redirected until they try and change pages.
I have a VueX action that triggers when the user's auth state changes but this (from what I can see), can't access the redirect or route variables.
// /mixins/auth.js
const reevaluateAuthStatus = (store, redirect, route) => {
console.log(route)
const redirectPolicy = route.meta.map((meta) => {
if (meta.auth && typeof meta.auth.redirectPolicy !== 'undefined') { return meta.auth.redirectPolicy[0] }
return []
})
const user = store.getters['auth/getUser']
if (redirectPolicy.includes('LOGGEDOUT')) {
if (user) {
return redirect('/dashboard')
}
} else if (redirectPolicy.includes('LOGGEDIN')) {
if (!user) {
return redirect('/login')
}
}
}
module.exports = {
reevaluateAuthStatus
}
// /middleware/auth.js
import { reevaluateAuthStatus } from '../mixins/auth'
export default function ({ store, redirect, route }) {
reevaluateAuthStatus(store, redirect, route)
}
Appreciate any help on this :)
You cannot re-evaluate a middleware AFAIK because it's mainly this (as stated in the documentation)
middlewares will be called [...] on the client-side when navigating to further routes
2 clean ways you can still achieve this IMO:
use some websockets, either with socket.io or something similar like Apollo Subscriptions, to have your UI taking into account the new changes
export your middleware logic to some kind of call, that you could trigger again by calling the $fetch hook again or any other data-related fetching hook in Nuxt
Some more ugly solutions would probably be:
making an internal setInterval and check if the actual state is still valid every 5s or so
move to the same page you are actually on with something like this.$router.go(0) as somehow explained in the Vue router documentation
Still, most of the cases I don't think that this one may be a big issue if the user is logged out, because he will just be redirected once he tries something.
As if the user becomes logged-in, I'm not even sure on which case this one can happen if he is not doing something pro-active on your SPA.
I don't know if it's relevant or not, but I solved a similar problem this way:
I have a global middleware to check the auth status. It's a function that receives Context as a parameter.
I have a plugin that injects itself into context (e.g. $middleware).
The middleware function is imported here.
In this plugin I define a method that calls this middleware passing the context (since the Plugin has Context as parameter as well): ctx.$middleware.triggerMiddleware = () => middleware(ctx);
Now the middleware triggers on every route change as intended, but I can also call this.$middleware.triggerMiddleware() everywhere I want.

Cypress doesn't work with an external login

I'm working on e2e test with cypress on my application.
In my case the login are manage by a external service.
When I want to enter in my application's home page (https://myApplication/home), the system redirects me in different superdomains to login.
At first cypress seems to be able to change the superdomain, but once arrived in external service page for the authentication, the system go in login error (as if we have already logged in, but incorrect).
This type of behavior does not happen outside the cypress .
Are there alternative solutions to manage external access in a cypress test or is it possible to manage it directly from cypress?
I added in my cypress.json the chromeWebSecurity:false and when I call the link for login, I added the failOnStatusCode: false,
but it still doesn't work.
Assuming this is caused by SameSite cookie blocking , then I've just been fighting the same issue. I resolved it by intercepting all requests, checking if they had a set-cookie header(s) and rewriting the SameSite attribute. There's probably a neater way to do it, as this does clutter up the cypress dashboard a little.
Sadly Zachary Costa's answer no longer works as Chrome 94 removed the SameSiteByDefaultCookies flag.
You can add this as a command for easy reuse:
In your commands file:
declare namespace Cypress {
interface Chainable<Subject> {
disableSameSiteCookieRestrictions(): void;
}
}
Cypress.Commands.add('disableSameSiteCookieRestrictions', () => {
cy.intercept('*', (req) => {
req.on('response', (res) => {
if (!res.headers['set-cookie']) {
return;
}
const disableSameSite = (headerContent: string): string => {
return headerContent.replace(/samesite=(lax|strict)/ig, 'samesite=none');
}
if (Array.isArray(res.headers['set-cookie'])) {
res.headers['set-cookie'] = res.headers['set-cookie'].map(disableSameSite);
} else {
res.headers['set-cookie'] = disableSameSite(res.headers['set-cookie']);
}
})
});
});
Usage:
it('should login using third party idp', () => {
cy.disableSameSiteCookieRestrictions();
//add test body here
});
or alteratively, run it before each test:
beforeEach(() => cy.disableSameSiteCookieRestrictions());
We were encountering a similar issue, where Cypress was redirecting us to the default "You are not logged in" page after getting through the login process. I'm not certain if that's EXACTLY the issue you were experiencing, but just in case, here's our solution. In our case, the issue was caused by Chrome's "Same Site Cookies" feature interacting poorly with Cypress, so we needed to disable it. In your plugins/index.js file, you would add the following code:
module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-features=SameSiteByDefaultCookies');
}
return launchOptions;
});
};
Note that if you already have launchOptions being set, you can just add this code onto it so it doesn't clash at all.
Hopefully, this works for you as well!
In the current version of cypress you can't go to another domain in the same test. This is due to the fact that cypress injects its test into the browser (they are working on this issue).
So one solution today is that you need to utilize cy.request to perform the login programmatically and inject the auth secret (jwt, cookie, localstorage, token or what you have) into the browser context yourself (for cookie this would be cy.setcookie).
Always make sure to checkout the plugins if there is already an abstraction for your login. Often this is openId or ntlm.

Nuxt auth SSR cookie issue only when closing browser and loading the page

I use nuxt and nuxt-auth-next, With a laravel api backend.
When i completely close browser and load the page nuxt cannot read cookies on server-side and wont be able to understand that i am logged in, and on client-side it understands that i have cookie so there will be a difference in my server-side and client-side loading some component based on user being logged in, and then, obviously, this will happen:
[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.
Now when i refresh this page. the problem will go away and everything will be ok.
I also checked if cookies are not availble on server-side when loading for the first time:
if (process.client) {
console.log('cookie = ', this.$cookies.get('auth._token.laravelJWT'));
} else {
console.log('cookie = ', this.$cookies.get('auth._token.laravelJWT'));
}
The result on console will be this:
Nuxt SSR
cookie = undefined
Client:
cookie = Bearer blablabla
It's very likely that your cookies are set to session-only (default for nuxt auth module), which explains why you face problems after you close the browser. To fix it, given them an expiration date, so the server will understand this is an authenticated user.
To expires in 8 days, your nuxt.config.js should look like this:
export default {
ssr: true,
...
auth: {
cookie: {
options: {
expires: 8
}
},
...
},
}
From https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies :
Session cookies are deleted when the current session ends. The browser
defines when the "current session" ends, and some browsers use session
restoring when restarting, which can cause session cookies to last
indefinitely long. Permanent cookies are deleted at a date specified
by the Expires attribute, or after a period of time specified by the
Max-Age attribute.
If you use nuxt-auth, and you want to avoid the expiration of the session cookies so the closing browser problem, you can set the expires and maxAge attributes in nuxt.config.js :
auth: {
...
cookie: {
options: {
expires: new Date(new Date().getTime()+20000000000).getTime(), //thats today + a year
maxAge: 31622400
}
}
},
this code is from nuxtServerInit does not receive auth cookies
i had this problem while ago.
when your project runs in server side you can access your cookies with this package "cookieparser"
this is the way i get user token in server-side :
const cookieparser = process.server ? require('cookieparser') : undefined
async nuxtServerInit ({ commit }, { req }) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
// if we have a token , get user data with api
if (parsed.access_token) {
}
}
}

Checks in vue-router.beforeEach not restricting access to routes

First off, I'm using vuejs 2.0 with webpack, vue-router (in history mode - SPA site), and vuex running on the webpack dev server with hot module loading.
I have about 10 routes mapped out to components. Site is working great, but now I'm adding some token based authentication. I'm using router.beforeEach to perform a token check. If the token is valid, it should let them through. If the token is NOT valid, it should redirect them to the /login page. The problem is that it performs the check and restricts it the first time. But the second attempt allows me to go to the page and display the contents. Every second request seems to process the route properly and redirect to /login. My checkToken() function is always returning false, for testing purposes.
Code:
// Configure Router
const router = new Router({
routes, //routes are configured elsewhere, they work fine so not needed
mode: 'history'
})
router.beforeEach((to, from, next) => {
if(to.path != '/login') {
if(checkToken()) {
logger('There is a token, resume. (' + to.path + ')');
next();
} else {
logger('There is no token, redirect to login. (' + to.path + ')');
next('login');
// next('/login');
// router.push('login');
}
} else {
logger('You\'re on the login page');
}
next();
});
function checkToken() {
return false;
}
Going to the main page ("/"), it redirects to /login as expected. In my console, I have the following 2 entries:
[ 14:36:30.399 ] : There is no token, redirect to login. (/)
[ 14:36:30.399 ] : You're on the login page
Seems to be good. It tried to load "/" and saw there was no token, then redirected to /login where the check sees that we're on the login page and stops.
Now I'll click on my Projects link that will take me to /project. Console output:
[ 14:40:21.322 ] : There is no token, redirect to login. (/project)
Perfect, but the projects page is actually being displayed rather than the login page.
Now I'll click on my Sites link that should take me to /site. Console output:
[ 14:41:50.790 ] : There is no token, redirect to login. (/site) [
14:41:50.792 ] : You're on the login page
Looks good, AND the browser is displaying the site page. This is exactly what I want to see.
Now I'll click on my Requests link that to /request. Console output:
[ 14:44:13.114 ] : There is no token, redirect to login. (/request)
but once again, it's not redirecting. I'm seeing my request page when I should be seeing my login page.
This time, I'll click on my Projects link again (/project) that incorrectly displayed the project page rather than the login page. Console output:
[ 14:47:12.799 ] : There is no token, redirect to login. (/project) [
14:47:12.800 ] : You're on the login page
This time, it redirected my to the /login page, as it should.
It is literally every other link I click that gets redirected appropriately, no matter what order or which link I click. First one redirects, second one doesn't, third one does, fourth doesn't, fifth does, etc...
I've tried next('/login'), next('login'), and router.push('login') and they're all the same result. It knows when it's supposed to redirect, but the redirect only works every other time.
If I do a full request (page refresh, type the address in and press enter), it will always redirect to /login as planned, but I'm trying to do this with an SPA. Anything I'm missing?
Fixed. Oversight on my part.
This is what the router code should have been:
router.beforeEach((to, from, next) => {
if(to.path != '/login') {
if(checkToken()) {
logger('There is a token, resume. (' + to.path + ')');
next();
} else {
logger('There is no token, redirect to login. (' + to.path + ')');
next('login');
}
} else {
logger('You\'re on the login page');
next(); // This is where it should have been
}
// next(); - This is in the wrong place
});
Silly issue with a simple answer, I had my next() in the wrong place so it was always processing it at the end. I am still curious why it ever redirected correctly, though.