There are a couple ways - one where you can pass it over the websocket when using Subscriptions (adding the user to the context), or the other where you pass the user id with each request. I prefer the second method because you don’t need to manually close/re-open socket connections if a user logs out and a different one logs in.
Example using websockets:
Server
const context = {}; //this gets passed to createApolloServer...
new SubscriptionServer({
schema,
execute,
subscribe,
// on connect subscription lifecycle event
onConnect: async (connectionParams, webSocket) => {
let subscriptionContext = context;
if (connectionParams.userToken) {
//if user token is given, add user to context
const lookupUser = function(context, userToken) {
const hashedToken = Accounts._hashLoginToken(userToken);
const user = Meteor.users.findOne({ "services.resume.loginTokens.hashedToken": hashedToken });
if (user) {
context.user = user;
}
return context;
};
//must use async/await, otherwise database lookups will fail (not sure why?)
subscriptionContext = await lookupUser(context, connectionParams.userToken);
}
return subscriptionContext;
}
}, {
server: WebApp.httpServer,
path: '/subscriptions'
});
Client
const link = ApolloLink.split(
operation => {
const operationAST = getOperationAST(operation.query, operation.operationName);
return !!operationAST && operationAST.operation === 'subscription';
},
new WebSocketLink({
uri: wsUri,
options: {
reconnect: true, // tells client to reconnect websocket after being disconnected (which will happen after a hot-reload)
// carry login state from client
// it is recommended you use the secure version of websockets (wss) when transporting sensitive login information
connectionParams: {
userToken: localStorage.getItem("Meteor.loginToken")
}
}
}),
new HttpLink({ uri: httpUri })
);
Now you should have a user
object in your context
for each resolver if the user is successfully logged in. The pitfall with this method is if a user is logged in, then logs out and another user logs in during the same session, the old login token from the previous session will persist as long as the websocket connection is open. I had a hard time getting this to work properly.
Example passing user token with each request
The other (in my opinion, easier and cleaner) solution is to pass the user token with each query. This way, it is guaranteed the user must have a valid login token with each request, and it doesn’t live in websocket connections (so this will work with regular requests as well).
Client (using blaze-apollo)
const result = Template.instance().gqlQuery({
query: GET_DATA,
variables: {
userToken: localStorage.getItem("Meteor.loginToken")
}
}).get();
Server (in your resolvers.js)
const resolvers = {
Query: {
getUser(obj, args, context) {
//check token exists
if (!args.userToken) {
throw new Meteor.Error("not-authenticated", "You must be logged in");
}
//validate token
const hashedToken = Accounts._hashLoginToken(args.userToken);
const user = Meteor.users.findOne({ "services.resume.loginTokens.hashedToken": hashedToken });
if (!user) {
throw new Meteor.Error("not-authenticated", "You must be logged in");
}
//user is logged in, do stuff if you want...
return user;
}
}
};
You will have to validate the user token with in each resolver, but you could abstract this and you would have to do this anyway if it were typical REST queries. You’ll know for sure, though, the user is valid.
Hope that helps.