Uncaught Error: Meteor.userId can only be invoked in method calls or publications

Hey guys,
I’ve just updated a Meteor 2 application to version 3. I’m using the SSR package to generate some initial HTML content. In our components, I do use “Meteor.userId()” which worked fine with fast-render before. After the upgrade, I receive the following error:

Uncaught Error: Meteor.userId can only be invoked in method calls or publications.

After some research I tried to remove all possible packages, but the error didn’t hide. This is my current package list after removing packages like fast-render or publish-composite:

meteor-base@1.5.2             # Packages every Meteor app needs to have
mobile-experience@1.1.2       # Packages for a great mobile UX
mongo@2.0.2                   # The database Meteor supports right now
reactive-var@1.0.13            # Reactive variable for tracker
tracker@1.3.4                 # Meteor's client-side reactive programming library

standard-minifier-js@3.0.0    # JS minifier run for production mode
es5-shim@4.8.1                # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.9              # Enable ECMAScript2015+ syntax in app code
typescript@5.4.3              # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.6.0            # Server-side component of the `meteor shell` command

static-html@1.4.0
server-render@0.4.2
accounts-password@3.0.2
react-meteor-data
check@1.4.4
standard-minifier-css@1.9.3
random@1.2.2

Just for example, this component will cause the error:

function Start(props) {
        const { username } = useTracker(() => Meteor.user());
        return (<div>{username}</div>);
}

Does anybody have an idea how we can use Meteor.userId() on a SSR component? Meteor.user() is also not working and shows the same error.

// Edit: Am I right in thinking that this functionality was exclusively available via fast-render?

For SSR, yes, Meteor.userId() handling is a feature explicitly implemented by fast-render wherein cookies are used to pass the login tokens of the previously logged-in user from the same device. Since DB queries become async, the user-specific handling of fast-render must be updated to support Meteor 3.

1 Like

On the client, you can get the Meteor.loginToken like this:

localStorage.getItem("Meteor.loginToken")

Then you can pass that to the server and get the Meteor User and userId like this:

const USER_TOKEN_PATH = 'services.resume.loginTokens.hashedToken';
export const NO_VALID_USER_ERROR = 'NO_VALID_USER';

export async function getUserIdByLoginToken(loginToken) {
    // `accounts-base` is a weak dependency, so we'll try to require it
    // eslint-disable-next-line global-require, prefer-destructuring
    const { Accounts } = require('meteor/accounts-base');

    if (!loginToken) { console.log(NO_VALID_USER_ERROR)}

    if (typeof loginToken !== 'string') {
        console.log("GraphQL login token isn't a string");
    }

    // the hashed token is the key to find the possible current user in the db
    const hashedToken = Accounts._hashLoginToken(loginToken);

    // get the possible user from the database with minimal fields
    const fields = { _id: 1, 'services.resume': 1 };
    const user = await Meteor.users.rawCollection()
        .findOne({ [USER_TOKEN_PATH]: hashedToken }, { fields });

    if (!user) { console.log(NO_VALID_USER_ERROR)}

    // find the corresponding token: the user may have several open sessions on different clients
    const currentToken = user.services.resume.loginTokens
        .find((token) => token.hashedToken === hashedToken);

    const tokenExpiresAt = Accounts._tokenExpiration(currentToken.when);
    const isTokenExpired = tokenExpiresAt < new Date();

    if (isTokenExpired) { console.log(NO_VALID_USER_ERROR)}

    return [user, user._id];
}

Ah, after all these years I totally forgot that Meteor.userId() on the server was enbalbed by fast-render :smiley:

@vikr00001 This solution will not work since we need the data already during SSR. fast-render sets a cookie and so the server gets the login token and can fetch the user doc. It seems that there is already a new version for fast-render that should support Meteor 3, but it is currently not working. Maybe that should be a solution for the Meteor core? Cookie authentication is used for decades now and SSR is the way to go. So Meteor.userId() should be available on the server, too.

1 Like

according to this question, I would like to asky about security and manipulation with loginToken. Isn’t it better to add the token into the request header? Isn’t also better to somehow encrypt the login token before add into the request?

…or isn’i it better to create new separate token for this method and do not use same token like in methods and publications?

what do you think?

Soo checked the code for fast-render v5 and submitted a PR to make the initial data work again :-). I’ve tried also to fix the Meteor.userId() issue on the server side, but I’m currently not sure how we would replace this line of code?

Meteor v2

      // support for Meteor.user
      Fibers.current._meteor_dynamics = [];
      Fibers.current._meteor_dynamics[DDP._CurrentInvocation.slot] = this;

The current maintainer replaced it with this:

const _meteor_dynamics = Meteor._getValueFromAslStore('_meteor_dynamics') || [];
_meteor_dynamics[DDP._CurrentInvocation.slot] = this;

But this gives me everytime an empty array, also it seems that we don’t manipulate any value here that will be used by Meteor later.

// Edit: Okay, replacing

  - frContext: new Meteor.EnvironmentVariable(),
  + frContext: DDP._CurrentInvocation,

did the trick. Now Meteor.userId() is also available on the server (within the FastRender context). Normally fast-render should now be ready for Meteor 3.

3 Likes