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:
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.
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
@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.
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?
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;
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.