Accounts.passwordless (and Vue)

(This is part of a slack communication thread, and placed here as a backup as Slack threads aren’t available after 3 months for many)

Slack thread

I’m using Meteor 3 (+Vue) with Accounts.passwordless and I’m running into unknown territory.
I’ve searched the docs, and web for answers, I’ve also tried to get some insights via AI but to no avail.
When using Accounts.requestLoginTokenForUser the user receives an email with an url.
That url doesn’t have a route in my router, yet when the url is clicked the user is automatically signed in without any coding on my part. And the user doesn’t know either because it doesn’t show. I have UI that should show if a user is logged in, but that only shows when you reload the page.

When I create a route, for example:

{ path: '/:loginToken/:selector', name: 'url_sign_in', component: UrlSignInView, meta: { requiresAuth: false } }

Then when the url is requested it isn’t used. I’ve created a basic component with only an alert and console.log on it and those don’t fire.

I wish I had some control in this. Getting data after log in and redirect user to the correct view etc.
When looking at the source code (meteor/packages/accounts-passwordless/passwordless_client.js at devel · meteor/meteor · GitHub) I see “Accounts.autoLoginWithToken” but I can’t find any documentation on this or if it can be disabled.

I have tried to set it in Accounts.config:

Accounts.config({
    tokenSequenceLength: 8,
    loginTokenExpirationHours: 0.5,
    autoLoginWithToken: false,
});

But this isn’t valid, because I get the following message: Accounts.config: Invalid key: autoLoginWithToken
Can someone provide more insight into this? Thanks in advance. (edited)

(this part might be of interrest: meteor/packages/accounts-passwordless/passwordless_client.js at devel · meteor/meteor · GitHub )

(More from the Slack thread)

Victor reacted with a valid point/idea as follows:
I guess you could override Accounts.autoLoginWithToken as an empty function to prevent to autoLogin behavior:
Accounts.autoLoginWithToken = ()=>{}

The routing magic which appears with some packages is a bit frustrating because it’s hard to find the source code related to it but it usually works as is and doesn’t need to be modified (like the oauth code here : meteor/packages/oauth/oauth_client.js at devel · meteor/meteor · GitHub )

If I were you, I would not override the default behavior but try to fix the UI using maybe another server call if needed.

The UI should show if the user is connected. Are Meteor.userId() and Meteor.user() correct on the client after auto login and before reloading the page ?

Me:
Problem is that no calls I found will react to the auto login / URL sign in. I tried in server/main to add Accounts.onLogin that will fire at startup but it doesn’t seem to fire with auto login.
The auto login does its thing and no UI updates seem to be called. So Meteor.userId() isn’t readable unless I do a reload. After the reload Meteor.userId() is available and can be shown either in the view or in the console.

Victor:
sounds like a bug to me. Meteor.user() and userId() should be set after autologin. I haven’t used passwordless yet so I can’t help you much.

After many hours spend on trial and error I came to the following solution:

In server/main.js I added this piece of code:

Accounts.onLogin(function(loginResult) {
    //console.log('loginResult: ' + JSON.stringify(loginResult));
    //console.log('loginResult.methodName: ' + loginResult.methodName);
    if (loginResult.type === 'passwordless' && loginResult.methodName === 'login') {
       Meteor.users.updateAsync(loginResult.user._id, {
          $set: {
             'profile.shouldNavigate': true,
             'profile.navigationPath': '/dashboard',
          },
       });
    }
});

To be perfectly honest, it works but I have no idea if this is the right solution. So if there is someone who can confirm this or provide a better solution, please do tell.

:+1: Thanks to Alim for pointing out to me that Slack threads are only visible for 3 months (for many users) so it would not help anyone who’s experiencing the same issue after that time.

2 Likes

I’m not familiar with vue but this is how our team implement this in React.

We have a component that subscribes to Meteor.user() for any changes including being logged in or not. Other components requiring a logged in user depend on this component.

In this setup, it doesn’t matter where or how the user logs in. This component updates and the dependent components updates their status for a logged in user. This includes a component that redirects the logged in user to another page if the current page is only accessible for guests e.g. login page.

The way which @rjdavid described is the best option in my opinion. I do something similar in a Vue based app.

If you are using a central store like Vuex or Pinia, what I would suggest is to have a component that is always present in the app (i.e. does not depend on the route, is called directly within app.vue) which subscribes to Meteor.user() and then puts the result in the central store under “user”. And then everywhere in your app you can just look if the user object is present. Primarily, in vue.app you could then decide what to show in case there is no user object. For example, if you see Meteor.userId and the user object is empty, you know that the user is being logged in and you can show a spinner before showing any route content.

This is much simpler and less error prone than trying to do any redirects manually on specific routes.

Thanks @rjdavid & @vooteles for looking into this,

I’m using a similar setup as both of you describe (with Pinia).
The problem is not in components or routes but only at the specific time when a user does log in with the passwordless url (which doesn’t trigger any routing**) from their email. No view / component seems to get triggered until the user reloads the view or clicks on a link to go to another view.

The user is logged in and the user object is set, but no visual clue for the user is triggered. This is a weird experience for the user because the last view is the view where they requested their token. So the user could see this a noting happend and tries to request a token again, and by clicking some route-link the app ‘reloads’ and the correct parts fire and then the user sees that the are logged in.

So I really need some sort of a a redirect to force a ‘reload’ after the email url is clicked, so it makes more sense to the user when they click on the link from their email that they are shown some confirmation that they are logged in.

** in the routes you can set whatever route you want and create with that route the url to be mailed, but the route isn’t called just a simple alert or console log aren’t triggered.

Since the user object is set, we know that the problem is not about getting the data from the server but rather something on client side.

One thing you might try is to see what is the default value of the object field in the strore. It is probably something like undefined, null or similar. See if anything changes if you put {} as the default value. There’s a specific limitation with vue (2?) reactivity that sometimes comes to bite with objects.

Edit: Had a quick glance here Reactivity in Depth — Vue.js Seems unlikely that my above comment could help here. But in any case, your issue seems fully client side since the user object is populated correctly in the store. So somewhere the local reactivity breaks.