Meteor.UserId() lost on browser refresh

I need some input from the Meteor community.

I use the Meteor.UserId() to check if the user has logged in or not. This works well when the user is not logged in(Meteor.userId() is null) and when the user has logged in(Meteor.userId() is not null) however if the user is logged in and the browser is refreshed Meteor.userId() is lost.

What are the community recommendations to keep track of whether the user has logged in or not? Should I redirect the user to the login page if the user has logged in and the browser is refreshed? Should I keep the Meteor.UserId() in local storage work after the user has logged in(I don’t think it will work)?

Update 1: The MainNavBar shows if the user is logged in or not based on Meteor.userId(). in a method call to App.vue.

MainNavBar.vue

<li class="nav-item d-xl-block" v-if="(this.$root.currentUserId==null)">
            <router-link class="nav-link" v-bind:to="{ name: 'register' }">Register</router-link>
          </li>
          <li class="nav-item d-xl-block" v-else>
            <a class="nav-link" href='' data-cy="logout" v-on:click="Logout()">Log out</a>
          </li>

App.vue:

<script>
import { mapGetters } from 'vuex';
import { Meteor } from 'meteor/meteor';
export default 
{
  computed: {
    ...mapGetters('layout', ['showCart',]),
  },
  meteor: 
  {
    $subscribe: 
    {
      'currentUserId': function() 
      {
        return [Meteor.userId()];
      },      
    },
    currentUser()
    {
      return Meteor.user();
    },
    currentUserId() 
    {
      return Meteor.userId();
    },
  },
}
</script>

Update 2:

src\imports\server.js:

import { Meteor } from 'meteor/meteor';
import './server/accountsBase';
import { Accounts } from 'meteor/accounts-base';
const isDev = process.env.NODE_ENV !== 'production'
Meteor.publish('currentUserId', function () 
{
  return this.userId;
});
Meteor.startup(() => 
{
  Accounts.config({loginExpirationInDays: 0.0006});
});

src\imports\api\publications.js

import { Meteor } from 'meteor/meteor';
Meteor.publish('currentUserId', function () 
{
  return this.userId;
});
Meteor.publish(null, function () 
{
    if (this.userId) 
    {
      return Meteor.users.find({ '_id': this.userId });
    } 
    else 
    {
      this.ready();
    }
})

Login.Vue:

<template>
              <form role="form">
                  <button type="button" v-on:click="LoginUserForDomain()">
                    Sign in
                  </button>
              </form> 
</template>
<script>
import { Meteor } from 'meteor/meteor';

export default 
{  
  methods: 
  {     
    LoginUserForDomain() 
    {               
	  this.disableButton=true;
	  Meteor.loginWithPassword(email, this.user.password, (error2)=>
	  {
	    this.disableButton=false;
	    if(error2)
	    {
	      this.failureMessage='There was an error logging you in. Our administrators have been notified of the issue and we will have a look.';
	      return;
	    } 
	    else 
	    {
	      root.setValue(StateVariables.SelectedDomain, 'ClearCrimson');
	      router.push({ name: 'dashboard', params: { domain: 'ClearCrimson' }});                   
	      return;
	    }
	  });
    },
  },
}
</script>

Update 3:
I am using vue-meteor and i was asked to check the following issue on stackoverflow.

What is your stack like? Are you using React, Apollo?

I am using meteor 1.8.3 with vuejs for the front end and Mongodb at the backend.

I have never used Vue, but if I understand you correctly, your looking to redirect the user if they are not logged in?

It appears to be a data reactivity issue, you have to make sure the data from the server has arrived before you check it.

Can you share the snippet where you make the check of Meteor.userId() ?

Hello @tunifight, I have updated the question above with more detail (and code).

Hello @jpperrin, i have update the question with more detail (and code) . I am looking for how the system should determine if the user has logged in and also how it should handle browser refreshes, after the user has logged in.

Something is wrong somewhere if you are losing the value of Meteor.userid() on browser refresh.

On load, if the user is logged in, Meteor.userId() will be set while waiting for the user data available in Meteor.user()

So normally, we create a publication for the current user data if Meteor.userId() exists. Then we will wait for the user data until the publication is ready. We do not wait if Meteor.userId() is null because we immediately know that the user is not logged in.

Are you doing any related login handler during startup / init which might reset the user data?

Thank you @rjdavid for your input. I have looked at the code and I do not see anything wrong but again, I have been working on Meteor, Vuejs for just 3 months so I am sure I am missing something very obvious.

I have added the server side publish\starup code in update 2 above. The client side startup code uses normal vuejs code but it is available here:

src/imports/client.js:

import 'intersection-observer';
import 'vue-googlemaps/dist/vue-googlemaps.css';
import { Meteor } from 'meteor/meteor';
import CreateApp from './app';
Meteor.startup(() => 
{
  CreateApp({});
});

src/imports/app.js

import 'isomorphic-fetch';

import './plugins';
import './supply';

import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import { sync } from 'vuex-router-sync';
import { injectSupply } from 'vue-supply';
import App from './client/ui/App.vue';
import routes from './routes';
import storeOptions from './store';

function createApp () {
  //importing and registering various vuejs components. 
  const router = new VueRouter({mode: 'history', routes,});
  return {
    app: new Vue({
      el: '#app',
      router,
      ...App,
    }),
    router,
  }
}
export default createApp

Not overly familiar with Vue, and I don’t see any call to Meteor.userId() anywhere in the code examples in the post here, however, I’ll just throw it out there in case it helps - when refreshing the page, there is a slight delay, at which point:

Meteor.loggingIn() will evaluate to true

What I generally do in my React apps is to not show anything on screen while this loggingIn check is going on (if the route they are on requires authentication). In that case, there is no flickering and after the check is complete, Meteor.userId() should return as you expect.

Also worth checking (if the above doesn’t work) is whether the JWT is still there (Chrome devtools would be the easiest way to check this I think?) on page refresh. This would be a weird error but I vaguely recall seeing something (here or elsewhere) where some code or something was wiping out the JWT on refresh, causing a similar issue.

In my vue application I use Vue’s navigation guards

https://router.vuejs.org/guide/advanced/navigation-guards.html

and in that guard function i will check for whether Meteor.userId() is null or not. so if your trying to go to a route the isnt /login and you have no Meteor.UserId() you are redirected to the login page

I thought I was having issues with my Meteor.userId() not having the value I was expecting so what i did was I added it’s value to my App.vue template’s markup so i could see it all the time and monitor it easily

<script>
import { Meteor } from "meteor/meteor";
import Vue from "vue";
export default {
  data() {
    return {
      userId: "",
      isLoggedIn: false
    };
  },
  methods: {
    logOff: function(event) {
      Meteor.logout((function (err) {
                  if (err) {
                    this.$toasted.error(err.reason);
                  } else {
                    this.$router.push({ path: '/login' })
                  }
            }).bind(this));
     
    }
  },

  mounted() {
    this.$autorun(() => this.isLoggedIn = (Meteor.userId() != null));
    this.$autorun(() => this.userId = Meteor.userId() );
  }
};
</script>

you can use something like this in you App.vue’s script tag to update the value when the App.vue component is mounted. Then add {{ userId }} to your markup somewhere and watch it on a refresh.

Thank you @brianmulhall for your input. For the time being I have implemented the Vue’s navigation guards in my application to redirect the user to login screen if the Meteor.userId() is lost.

Thank you @brianmulhall for your input. I am thinking that my issue has something to do with the publish(in publications.js above), subscribe(in App.Vue above) code that I have written.
Would it be possible to share code snippets for your publish, subscribe code that you have written, for this issue?

If you wanna check out the documentation about what data from the User collection is published to the client I included a link above.

This is where I publish the user’s document and then my subscription as well.

Meteor.publish("userData", function () {
    if (this.userId) {
        return Meteor.users.find( { _id: this.userId } );
      } else {
        this.ready();
      }
});
Meteor.subscribe('userData');

I am thinking that the Meteor.userId() will be accessible to you even without that defined tho. I dont have autopublish installed and when i remove that publication/subscription pair I can still query the Meteor.user() method and retrieve a username, email and user id

Based on the comment, I need to use global navigation guards that will make the router wait for the user subscription to be ready.

I have the following code which redirects the user to the login page if the Meteor.userId() is not present.
How can I change the code to make the “router wait for the user subscription to be ready”, using Meteor.loggingIn(). Meteor.loggingIn() is a reactive data source.

router.beforeEach((to, from, next) => 
  {
    //I need to wait here till Meteor.loggingIn() returns true? 
    let loggingIn= Meteor.loggingIn();
    if (loggingIn && Meteor.userId() == null && 
      to.name ==='dashboard'
    {
      next('/login');
    }
    else 
    {
      next();
    }
  });

I ran into the same issue and solved it by having the router wait for loggingIn(). Using Iron Router, I created two functions to do the waiting, one that waits for loggingIn() and the roles subscription (in case I need to check for admin privileges), and the other that just waits for loggingIn().

var waitOnUser = function() {

  return [
    {
      ready: () => {
        return !Meteor.loggingIn();
      }
    },
    Meteor.subscribe("roles")
  ];
};

var waitOnUserNoRoles = function() {
  return [
    {
      ready: () => {
        return !Meteor.loggingIn();
      }
    }
  ];
};

And then in any route that needs it, use the waitOn and data options as follows:

Router.route("/myRoute", {
  name: "myRoute",

  waitOn: waitOnUser,

  data: function() {
    if(this.ready()){
      return {};
    }
  }
});

The route waits for those subs, and then returns the data after they’re ready, which then in turn allows the page to to start loading.

Thank you @hanley for the hand holding.

1 Like