Nuxt - login without redirect - authentication

I'm trying to create authentication on my Nuxt app, but every tutorial I've found is using redirect to public / private paths.
Example:
if (user.isLoggedIn()) {
redirect('/dashboard')
} else {
redirect('/login')
}
I'm used to react way, where I have a single wrapper component in which I decide by the state if I want to show public (login) or private (dashboard) page.
Example of index page (route path '/'):
export default = ({ viewer }) =>
viewer.isLoggedIn ? <Dashboard /> : <Login />
Is there any way to achieve this in Nuxt?

You have to set a dynamic layout parameter in your Page.vue files.
first step, set dynamic layout in your Page.vue:
export default {
layout (context) {
return context.isLoggedIn ? 'privateLayout' : 'publicLayout';
}
}
second step, set a Context custom var (or better, in your Store) from a middleware auth:
export default function (context) {
context.isLoggedIn = true; //or false, insert your auth checking here
}
see documentation: https://nuxtjs.org/api/pages-layout#the-layout-property
see live example: https://glitch.com/edit/#!/nuxt-dynamic-layouts?path=pages/index.vue:10:8

You can use your index page as a wrapper for two components that show depending on whether the user is logged in. So inside your index.vue:
<template>
<div class="wrapper">
<dashboard v-if="userIsLoggedIn" />
<login v-else />
</div>
</template>
Then you could write the dashboard and login component as separate pages and even switch dynamically between them by making userIsloggedIn reactive.
Hope that's more like what you're looking for.

Related

How can I refresh a route every time it is accessed in Vue Router 4?

I have setup a route in vue-router 4 that should load a component dynamically depending on whether the user is logged in or not. I did it like this (there may be a better way?):
import Personal from '../views/Personal.vue';
import Public from '../views/Public.vue';
routes: [
{
path: '/',
component: async () => {
const isLoggedIn = await authenticateUser();
if (isLoggedIn == true) {
return Personal
} else {
return Public
}
}
}
]
The App.vue file is this:
<template>
<div id="app">
<Site-Header></Site-Header>
<router-view></router-view>
<Site-Footer></Site-Footer>
</div>
</template>
The problem is that if a user logs in from the homepage route with path of '/', he doesn't navigate away from this route. Instead I would like vue-router to just load the Personal component instead.
The switch between Personal and Public only seems to work if I hard refresh the page, otherwise no changes happen. So if a user logs in, they still see the Public.vue component, then after a page refresh they see the Personal.vue component. If they then logout, they still see the Personal.vue component until they refresh the page.
How could I force vue-router to analyse the route after log-in/log-out and load the correct component?
To have multiple routes utilizing the same path, your best bet is using Named Views. You can define the default component for your index, or / path to be your Public component, while conditionally selecting a different component using v-if in your template.
You could define your routes as:
routes: [
{
components: {
default: Public,
Personal: Personal
},
name: "index",
path: "/"
}
]
Important to note that the syntax here differs. The component field here has to be pluralized in order for this to work, so it has to be components.
Then in your root template that's calling the views, you can then use a simple v-if to switch between them, depending on whether the user is logged in or not. How you store that information is up to you, so just adapt the below code to reflect your own app's logic
<!-- The default is Public, so you don't have to provide the name here -->
<router-view v-if="!user.loggedIn" />
<router-view v-else name="Personal" />
You can see this in this codesandbox

how to redirect to child route in nuxt?

i want to redirect to child route, if user entered to localhost:3000/browse,
he redirect to localhost:3000/browse/songs/new.
but i don't know how to do it!
and i don't know what is the best component structure for this example
my routes will be like this:
localhost:3000/browse/songs/new
localhost:3000/browse/songs/popular
localhost:3000/browse/songs/top
localhost:3000/browse/songs/podcasts
localhost:3000/browse/songs/playlist
and in all of theme, i have a common section, but contents are difrent.
common section
If you only have one location to redirect to, check out Andrew1325's answer. If there are multiple route-to-redirect match-ups, this thread has some ideas.
The way I do it is with "anonymous" middleware, like so:
In /pages/browse/index.js
<script>
export default {
middleware: [
function({ redirect }) {
redirect('/browse/songs/new');
},
],
};
</script>
To redirect automatically or under certain conditions you need to use middleware. This can be setup like this:
//browse/index.js
<template>
//no need for content as redirecting
</template>
<script>
export default {
middleware: 'redirect'
}
</script>
and your middleware file...
//middleware/redirect.js
export default function ({ store, redirect }) {
// automatic redirect
return redirect('/browse/songs/new')
}
Read about middleware here
Your pages structure is set up in the pages folder and you name folders and pages as you would like your routes to be.
Read about it here
To use a common theme you can use layouts. These will have a <nuxt-child/> section which will display the individual page content.
You can read about them here
All of this is pretty basic nuxt stuff, you should take some time to read the documantation or look at some tutorials listed here.
Big ups to #parker_codes, there is a little deficiency is his implementation which is circular redirection a.k.a forever redirect loop.
To solve this, add some checks in the parent component. This is because Nuxt Js calls the middleware of a parent route anytime a child route is visited.
//browse/index.js
<template>
//no need for content as redirecting
<NuxtChild />
</template>
<script>
export default {
middleware({ route, redirect, from }) {
if(route.path == "/browse" || route.path == "/browse/"){
return redirect("/browse/songs/new")
}
}
}
</script>

How to watch on Route changes with Nuxt and asyncData

Hi everybody i'm trying to watch on route changes in my nuxt js app.
Here my middleware:
export default function ({ route }) {
return route; but i don't know what to write here
}
index.vue File
middleware: [routeReact]
i'm trying to write this:
app.context.route = route
but it says to me that app.context doesn't exist
Here's the point of my question i'm trying to update my data that gets from my api with axios on page if route changing
like this
this the page
i'm clicking link to next page :
but when i'm route to next page, nothing happens all data is the same:
here my asyncData code:
asyncData({ app }) {
return app.$axios.$get('apps/' + app.context.route.fullPath.replace(/\/categories\/?/, ''))
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
};
})
}
Thanks for your help
First thing, context.route or it's alias this.$route is immutable object and should not be assigned a value.
Instead, we should use this.$router and it's methods for programmatic navigation or <nuxt-link> and <router-link>.
As I understand, you need to render the same route, but trigger asyncData hook in order to update component's data. Only route query is changed.
Correct way to navigate to the same page but with different data is to use link of such format:
<nuxt-link :to="{ name: 'index', query: { start: 420 }}"
Then you can use nuxt provided option watchQuery on page component and access that query inside asyncData as follows:
watchQuery: true,
asyncData ({ query, app }) {
const { start } = query
const queryString = start ? `?start=${start}` : ''
return app.$axios.$get(`apps/${queryString}`)
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
}
})
},
This option does not require usage of middleware. If you want to stick to using middleware functions, you can add a key to layout or page view that is used. Here is an example of adding a key to default layout:
<nuxt :key="$route.fullPath" />
This will force nuxt to re-render the page, thus calling middlewares and hooks. It is also useful for triggering transitions when switching dynamic routes of the same page component.

Why console.log() outputs in middleware are only visible on the server when entering page directly?

I tried to test if I can output something in the middleware only on certain processes. But if I use the following code - process.server seems always to work - also when I enter the route directly via browser. Other outputs are only visible when I change the route via router. I'm using Nuxt in the universal mode. What's happening there?
Actually I want to feed the store from localstorage user data and then redirect the user when this page is a guarded one. This could be only done from process.client where localStorage is defined. Can it be done with middleware at all? And also when entering the page directly?
middleware/test.vue
export default function (context) {
if (process.server) {
console.log('MIDDLEWARE SERVER')
}
if (!process.server) {
console.log('MIDDLEWARE NON-SERVER')
}
if (process.client) {
console.log('MIDDLEWARE CLIENT')
}
if (process.browser){
console.log('MIDDLEWARE BROWSER')
}
}
pages/test.vue
<template>
<h1>Some test Template</h1>
</template>
<script>
export default {
middleware: ['test']
}
</script>
After digging deep into this I found an answer from a Nuxt team member. Obviously this is the intended default behavior of middleware in the universal mode to run on page refresh only on server. The documentation wasn't that clear about it.
The only way to get stored data in the page refresh scenario is to use cookies like this.
//middleware/auth.js
export default function(context) {
context.store.dispatch("initAuth", context.req)
}
Then:
//store/index.js
actions: {
initAuth(vuexContext, req) {
if(req) {
if (!req.headers.cookie) {
return
}
// go get the cookie ;)
}
}
}

Aurelia: change navigation in app.js from view

I have an Aurelia project with navigation in app.html and app.js. The project includes a home page that has a different style to it, including navigation that is different than the non-home page views.
I would like to turn off navigation for the home view so I tried setting a variable (showMenu) to toggle the visibility. In fact, I am able to use jQuery to do this, but I wonder if there is an Aurelia way of doing it. If I set this.showMenu to true it shows the menu container, and false hides it. Like this for example:
app.html
<div class="container" if.bind="showMenu">
app.js
constructor(router){
this.router = router;
this.showMenu = true;
...other things
}
What I would like to do is set showMenu to false from home.js. I tried this (among 20 or so other attempts), but it does not work.
home.js
activate() {
this.showMenu = false;
}
Is there a way through $parent or some other means to hide the menu in app.html using a view model?
EDIT
This works but it feels a little like a hack.
home.js
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
#inject(Router)
export class Home {
constructor(router) {
this.router = router;
}
attached(){
$("#navbarMenu").hide();
this.router.refreshNavigation();
}
}
You should be able to use router to achieve that. Since this is required for one page only, you can have something like this assuming your route name is home (or you could use other properties of RouteConfig that you have set in configureRouter):
<div class="container" if.bind="router.currentInstruction.config.name !== 'home'">
I approach this problem by using separate shells. By default Aurelia will start your app with app.js (or ts). But you can change that default and also use the same command to redirect to a new shell after authentication.
In your main.ts (or .js) you will have a line to start your aurelia app:
aurelia.start().then(() => aurelia.setRoot());
This line is telling aurelia to start and to set the root view model for your app, when aurelia.setRoot() has no value given it defaults to app.ts (or .js).
So I create a landing for my app where I can display with the page and styles I wish completely separately from the main app, including a limited router and navigation.
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
aurelia.start().then(() => aurelia.setRoot('authPage'));
}
authPage.ts is my usual app.ts with a router configuration but it will only have the authPage configured in it and perhaps one or two other welcome pages.
The authPage takes care of authentication and obtaining appropriate tokens. I use a 3rd party for authentication services so all I have on this page is a link. Either way after successful authentication is confirmed you now just want to redirect to an alternative aurelia shell.
#autoinject
export class AuthPage {
private app : Aurelia;
private router : Router;
constructor(router : Router, app: Aurelia) {
this.app = app;
this.router = router;
}
authenticate {
//some kind of authentication procedure...
if(authenticationSuccess) {
this.router.navigate('/', { replace: true, trigger: false});
this.router.reset();
this.router.("authenticatedApp");
}
}
The lines this.router.navigate('/', { replace: true, trigger: false}); and this.router.reset(); are provided to deal with issues mentioned here and also on SO here. The shell switch line this.router.("authenticatedApp"); doesn't work for me without the other two.
My authenticatedApp configures a full router and navigation menu for the user in just the same way as you would normally do with app.ts but now separated into its own shell.
Of course there is nothing to prevent someone linking straight to authenticatedApp but at this point there is no data displayed without an api call which all require an access token to be presented.
This is a useful link on building an Aurelia app with multiple shells for authentication.
The end result is a separated landing pages and application pages which can have different styles and different navigation.On logout you can do the same thing in reverse to reload the auth page.