I need to add 2FA (OTP verification) after successful Google OAuth login, but Meteor’s OAuth packages don’t provide any way to interrupt the login flow.
Current Code
Meteor.loginWithGoogle({
requestPermissions: ['email', 'profile'],
loginStyle: 'popup'
}, (error) => {
setGoogleLoading(false);
if (error) {
// Handle errors
setError(error.reason || 'Google login failed');
} else {
// User is immediately logged in - NO way to stop here for 2FA
console.log('Google login successful');
onSuccess(); // This happens immediately, no 2FA step possible
}
});
The Core Issue
There’s no error condition or callback for 2FA in Meteor’s OAuth system. The flow is binary:
OAuth succeeds → User immediately logged in
OAuth fails → Error thrown
There’s no intermediate state or callback for additional verification steps.
What I Need
I want to intercept the login flow after Google OAuth succeeds but before the user session is created, so I can:
Get Google OAuth token/user info
Show 2FA/OTP prompt (No way to pause here)
Validate OTP
Complete login
Do I need to:
Abandon Meteor.loginWithGoogle entirely?
Implement custom OAuth flow manually?
Use some other approach?
Questions for the Community
Has anyone successfully extended existing 2FA systems to work with OAuth?
Are there hooks in Meteor’s accounts system I can use to intercept OAuth completion?
Should I implement custom OAuth flow to integrate with my existing 2FA methods?
Any packages that make OAuth providers work with custom authentication flows?
Since I already have the 2FA infrastructure working, I’m hoping there’s a way to “plug in” OAuth providers without rebuilding everything.
Why can’t it be after the OAuth login? Most cases I’ve seen are like:
login with google → ok → onboard user(ask it to add 2fa)
I’ll take a look around to see if there is an easy way to solve this. I’ve never done anything like this in Meteor but in other places I’ve done as I mentioned above
@grubba The “login completely then ask for 2FA” approach has i feel has some issues
The Issue (assuming this approach): If you let users complete OAuth login first, they’re fully authenticated in Meteor’s session system. This means:
Meteor.userId() returns their ID
Meteor.user() returns their data
They have a valid login token
They can access any route/method that checks if (Meteor.userId())
Unless handled carefully, you’d need to add 2FA guards to every single route, publication, and method - which defeats the purpose of having a secure authentication flow. The user is essentially fully logged in but artificially blocked, creating potential security gaps.
That said, I’m curious about your approach: “login with google → ok → onboard user(ask it to add 2fa)” - could you elaborate on how you handle the security concerns during that onboarding flow? Do you restrict access to certain routes/methods until 2FA setup is complete? I’d love to understand your implementation better!
I mean, they are logged in, but most apps that I have worked use a trust-level-like/role-like approach to auth, which means that your user authenticated but without 2fa is just a base user and should only be able to do base-level actions, if they want to do more risky actions, they should be an upper level, to be in that upper level they musk have a few requirements in place, one of them was the 2fa.
What we’re (@hkonda is part of my team) aiming for is a way to hook into the OAuth flow before Meteor gives the user full access. Essentially:
Let Google OAuth succeed—but pause before Meteor issues a Meteor.userId() and a session.
Kick off a 2FA prompt (OTP, whatever you’re using).
Only once that’s verified do you let the user fully log in.
As mentioned by @grubba, One approach could be to give users a temporary “2FA-required” role immediately after OAuth, then only remove that role once they complete the second factor. During that interim state, you’d treat them as “authenticated—but limited.” Any method or route that needs 2FA would include a check like:
if (!userHas2faRole(Meteor.userId())) {
throw new Meteor.Error('2fa-required', 'Please complete two-factor authentication');
}
Naturally, that means you’d need to audit your app and guard anything sensitive—every method, publication, and route—to ensure “2FA-complete” status is enforced.
On the upside:
It keeps your OAuth login intact—you’re augmenting it, not replacing it.
It lets you use Meteor’s built-in session/token system without rewriting your entire login system.
On the downside:
You must verify that 2FA checks are in place everywhere they matter.
It adds some complexity in onboarding flows or session expiration.
An alternative is to build a custom OAuth flow (e.g., using Accounts.oauth.requestCredential() and Accounts.oauth._loginHandlers) that allows you to intercept the flow after Google, pause for OTP, then finish the login manually—but that’s a bit more involved.
One thing that came to my mind was to use Tracker.autorun, Meteor.userId is reactive, so you can hook an function to run the value changes. Maybe that could help you?!
if (!userHas2faRole(Meteor.userId())) {
throw new Meteor.Error('2fa-required', 'Please complete two-factor authentication');
}
I would have some kind of static class/service or something so that it would centralize this kind of requirement:
if (!User.is("secured-user")) { //these are the users that have 2fa + credit card etc
throw new Meteor.Error('2fa-required', 'Please complete two-factor authentication');
}
or you could use hooks+functions enterily
Meteor.methods({
"foo": securedMethod((arg0,arg1) => { // this runs the code above and you can focus only on the code and it looks "clean"
})
})
The Trade-off: This works for OAuth + MFA but requires adding Tracker.autorun to every protected page.
Alternative Question: If custom method is the only way to achive the “pause”, can we use Accounts.registerLoginHandler for this? Could we register something like “googleMFA” and manually handle the OAuth flow using http ? is it possible ?
I got this idea, what about using the redirect_uri option, to a route like login/2fa and do the checking there, if he tries to move out of this screen he gets logged out/modal blocked.
login → redirected to 2fa check
→ if already has 2fa → continue to dashboard
→ If no 2fa → prompt to add → if tries to leave screen → get’s pop up or logged out.
Does this mean that the session of the user without 2fa is actually logged in? Can that session send a DDP method call through dev tools as a logged in user?
Yes! They effectively are logged in, but as a regular user, they can’t leave until they have their 2FA in place.
Yes, they can, that is why we should always be protecting both the client and the server(if it is an important method, we should always validate if the user is good to do the action and log the action that has been done)
→ If no 2fa → prompt to add → if tries to leave screen → get’s pop up or logged out.
Considering this approach , how would you render the page on client side ? like in Meteor 3.3 I am not able to use kadira:blaze-layout package , or would you do a server side rendering?. If we are doing server side rendering I am not sure how would we call something like
I traced through accounts-oauth, accounts-base, and accounts-2fa packages and modified updateOrCreateUserFromExternalService to handle 2FA flows seamlessly.