In meteor v2 Ive previously used meteor-rest to create a json api for my meteor app. meteor-rest is no longer supported, and in meteor v3 I understand that I can use Express and its ecosystem. Does anyone have any good pointers (beyond the migration guide) for doing this and:
creating endpoints to login (returning bearer token) and logout
creating middleware to use that bearer token in my custom rest routes
Just wanted to give a shoutout this example code, I don’t know how it came to be, but searching around for examples on how to bolt web api auth onto a Meteor app (especially post v3 with express), this example is very detailed and appreciated (edit: though I guess you’re still seeking advice - regardless, for lazier people like me it’s good to see what others are doing).
I might be missing something but I think login token lifecycle management might need to be addressed - that is, if it’s omission wasn’t by design.
Otherwise I can see a situation where you end up with people permanently authenticated until they access the Meteor DDP world and their token gets invalidated, then their API stops working?
(Or do stale tokens only get removed when a DDP client attempts to use that one specifically? So the web API tokens just hang around forever?)
Ahh, yes you’re quite right - many thanks for pointing this out. The MeteorRestAuthentication middleware needs to honour the tokenExpires mechanism. In my tests the following seems to do this:
// express middleware for authenticating a user resume token
export const MeteorRestAuthentication = (opts) => {
const bearerTokenMiddleware = bearerToken(opts)
const authMiddleware = async (req, res, next) => {
if (req.token) {
const hashedToken = Accounts._hashLoginToken(req.token)
const user = await Meteor.users.findOneAsync(
{
'services.resume.loginTokens.hashedToken': hashedToken,
},
{ fields: { 'services.resume.loginTokens': 1 } },
)
if (user) {
let resume = user.services.resume.loginTokens.find(
(token) => token.hashedToken == hashedToken,
)
if (resume) {
const tokenExpires = Accounts._tokenExpiration(resume.when)
if (new Date() <= tokenExpires) {
// update resume when
Meteor.users.updateAsync(
{ _id: user._id, 'services.resume.loginTokens': resume },
{
$set: {
'services.resume.loginTokens.$': {
hashedToken: resume.hashedToken,
when: new Date(),
},
},
},
)
req.userId = user._id
} else {
// token has expired so remove it
Meteor.users.updateAsync(
{ _id: user._id },
{ $pull: { 'services.resume.loginTokens': resume } },
)
}
}
}
}
next()
}
return [bearerTokenMiddleware, authMiddleware]
}
This approach will remove expired tokens but you might want to combine this with a periodic calling of Accounts._expireTokens() to remove those that are created and then forgotten. Im a little unsure of the consequences of asynchronously updating the user document like this but abstractly it seems sensible.
I found this later thread Simple:rest Meteor 3.x useful for more tips of what other people had done.
Im a little unsure of the consequences of asynchronously updating the user document like this but abstractly it seems sensible
I reckon the only issue is that if you get an unhandled rejected promise (maybe if the DB connection had a momentary glitch?) you could accidentally risk crashing the whole server now that we’re on a new version of Node for Meteor 3?
Haven’t seen it in practice before but in that case you might just want to log an error to start with.
My only other thought is that if you wanted long-lived API Tokens as a separate auth mechanism (e.g. stored in a different collection etc), then that might require a bit of refactoring because someone might want to use bearer tokens for that.
But that’d basically be duplicating what Meteor does for login tokens or passwords auth, so maybe a separate topic. Might be fun to explore in a new package.