
Description
Use when wiring up authentication in a Meteor 3 app. Triggers on accounts-password, accounts-base, OAuth (Google, Facebook, GitHub, Apple, Twitter, Meetup, Weibo), accounts-2fa, accounts-passwordless, ServiceConfiguration.configurations.upsertAsync, Accounts.createUserAsync, Accounts.setPasswordAsync, Accounts.forgotPassword, Accounts.resetPassword, Accounts.verifyEmail, useHttpOnlyCookies, clientStorage, Meteor.loginWithPasswordAnd2faCode, email verification. Use this skill when the user asks about signups, signins, or asks about token storage vs HttpOnly cookies.
SKILL.md
Meteor accounts
accounts-base plus a flavor package (accounts-password,
accounts-google, etc.). Meteor stores users in Meteor.users and ships
the client a resume token mapped to that document.
Decision flow
- Username + password? Add
accounts-password. - Social login? Add
accounts-baseplus the provider package (e.g.accounts-google) and configure the service. - Magic-link? Add
accounts-passwordless. - 2FA? Layer
accounts-2faon top ofaccounts-password. - Token storage on the client? Default is Web Storage. Meteor 3.3+ supports an HttpOnly cookie flow; see the section below.
Username + password
// server/accounts.js
import { Accounts } from "meteor/accounts-base";
import { Meteor } from "meteor/meteor";
Meteor.startup(() => {
Accounts.config({
sendVerificationEmail: true,
});
Accounts.emailTemplates.siteName = "My App";
Accounts.emailTemplates.from = "no-reply@example.com";
});
The from address is required; Meteor 3.5+ logs a server warning if you
omit it.
Do not set forbidClientAccountCreation: true when the client signup form
calls Accounts.createUser or Accounts.createUserAsync. The server rejects
that request with 403 Signups forbidden. For invite-only or administrator
provisioning, set the option on both client and server, remove the public
signup UI, and call Accounts.createUserAsync only from trusted server code
after its own authorization check.
// client/signin.js (Meteor 3.5+)
import { Meteor } from "meteor/meteor";
import { Accounts } from "meteor/accounts-base";
async function signUp({ email, password }) {
await Accounts.createUserAsync({ email, password });
}
async function signIn({ email, password }) {
await Meteor.loginWithPasswordAsync(email, password);
}
Client Accounts.createUser(options, callback) also remains supported.
Meteor.loginWithPasswordAsync was added in Meteor 3.5; on older 3.x use
the callback form of Meteor.loginWithPassword or wrap it in a Promise.
On the server use await Accounts.createUserAsync(options) and
await Accounts.setPasswordAsync(userId, newPassword).
HttpOnly cookies (Meteor 3.3+)
Default token storage is Web Storage. To move the resume token into an HttpOnly cookie and keep the client tab in-memory only:
// server
Meteor.startup(() => {
Accounts.config({
clientStorage: "none",
useHttpOnlyCookies: true,
});
});
// settings.json (public; surfaces the same flags to the client)
{
"public": {
"packages": {
"accounts": {
"clientStorage": "none",
"useHttpOnlyCookies": true
}
}
}
}
After restart and login, Meteor.loginToken* no longer appears in
localStorage; the browser receives an HttpOnly meteor_login_token
cookie. Each tab keeps its own in-memory credentials.
OAuth (Google example)
// server/oauth.js
import { ServiceConfiguration } from "meteor/service-configuration";
await ServiceConfiguration.configurations.upsertAsync(
{ service: "google" },
{
$set: {
clientId: process.env.GOOGLE_CLIENT_ID,
secret: process.env.GOOGLE_CLIENT_SECRET,
loginStyle: "popup",
},
},
);
// client
Meteor.loginWithGoogle(
{ requestPermissions: ["email", "profile"] },
(err) => { if (err) console.error(err); },
);
loginStyle: "redirect" for mobile or environments without
window.close / window.opener. Each provider ships its own
accounts-<service> package: accounts-google, accounts-github,
accounts-facebook, accounts-twitter, accounts-apple,
accounts-meetup, accounts-weibo.
To encrypt OAuth secrets at rest, add oauth-encryption and pass
oauthSecretKey to Accounts.config. This seals the provider application
secret in ServiceConfiguration.configurations.secret and the supported
provider-specific user token fields. It does not create a generic
Meteor.users.services.<provider>.secret field. See meteor-security.
Email verification and reset
// client
Accounts.forgotPassword({ email }, (err) => { ... });
// reset page
Accounts.resetPassword(token, newPassword, (err) => { ... });
// verify email
Accounts.verifyEmail(token, (err) => { ... });
Customize the URL pattern that lands in the email:
Accounts.urls.resetPassword = (token) =>
Meteor.absoluteUrl(`reset/${token}`);
Accounts.urls.verifyEmail = (token) =>
Meteor.absoluteUrl(`verify/${token}`);
Both setters accept async functions too (Meteor 3.x).
Customize the message:
Accounts.emailTemplates.resetPassword.subject = () => "Reset your password";
Accounts.emailTemplates.resetPassword.text = (user, url) =>
`Reset link: ${url}`;
Passwordless
Add accounts-passwordless. Request a one-time code or link, then submit the
code with the same selector:
Accounts.requestLoginTokenForUser(
{
selector: { email },
userData: { email },
options: { userCreationDisabled: false },
},
(error) => { if (error) console.error(error); },
);
Meteor.passwordlessLoginWithToken(
{ email },
token,
(error) => { if (error) console.error(error); },
);
Set userCreationDisabled: true for sign-in-only flows. Configure
tokenSequenceLength, loginTokenExpirationHours, and the
Accounts.emailTemplates.sendLoginToken template on the server. Treat the
token-request method as an abuse-sensitive endpoint and rate-limit repeated
requests.
2FA
accounts-2fa adds TOTP. The login flow:
- Client calls
Meteor.loginWithPasswordAsync(user, password)on Meteor 3.5+, or the callback form ofMeteor.loginWithPasswordon older 3.x. - Server rejects with
error.error === 'no-2fa-code'. - Client prompts for a code, retries with
Meteor.loginWithPasswordAnd2faCode(user, password, code, cb).
Anti-patterns
- Store the OAuth secret in
Meteor.settings.public. The client seespublic. Put secrets at the top level ofsettings.json. - Build a custom password hash.
accounts-passworduses bcrypt; trust it. - Bypass
forbidClientAccountCreationwith a method that takes a password. Same risk. - Run
Accounts.config({ ... })inside a deferred path. Call it at top level or inMeteor.startup.
See also
references/password-flows.mdreferences/oauth-services.mdreferences/eval-cases.md- For OAuth-secret encryption and CSP, see the
meteor-securityskill.
More skills from the agent-skills repository
View all 14 skillsmeteor-blaze
build and debug Meteor Blaze interfaces
Aug 28FrontendMeteorWeb Developmentmeteor-community-packages
manage Meteor community packages
Aug 28EngineeringMeteormeteor-debugging
diagnose failures in Meteor 3 applications
Aug 28DebuggingMeteorWebSocketsmeteor-deployment
deploy Meteor 3 applications
Aug 28DeploymentDockerKubernetesMeteormeteor-methods
author and debug Meteor methods
Aug 28API DevelopmentBackendMeteormeteor-modern-build-stack
configure Meteor 3 modern build stacks
Aug 28BuildMeteorPerformance
More from Meteor
View publishermeteor-mongo-minimongo
author and debug Meteor MongoDB queries
agent-skills
Aug 28DatabaseDebuggingMeteorMongoDBmeteor-pubsub
author and debug Meteor publications
agent-skills
Aug 28BackendMeteorReal-timemeteor-react
build and debug Meteor React interfaces
agent-skills
Aug 28FrontendMeteorReactWeb Developmentmeteor-security
audit and harden Meteor 3 applications
agent-skills
Aug 28AuthCode AnalysisMeteorSecuritymeteor-testing
write and repair Meteor test harnesses
agent-skills
Aug 28MeteorQATesting