About meteor-accounts
A skill for configuring Meteor accounts, covering password, OAuth, passwordless, two-factor authentication, token storage, and email flows. It guides Meteor developers through choosing account packages and handling configuration and security considerations.
Key features
- 01Password and account creation flows
- 02OAuth provider configuration
- 03Passwordless login tokens
- 04TOTP two-factor login
- 05HttpOnly cookie token storage
- 06Email verification and reset
Use cases
- 01Choosing an authentication package for a Meteor app
- 02Adding Google or other provider sign-in
- 03Moving resume tokens to HttpOnly cookies
- 04Implementing passwordless or TOTP sign-in
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ meteor/ agent-skills --skill meteor-accountsAfter installing, type/meteor-accountsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/eval-cases.mdhttp-only-cookies.mdoauth-services.mdpassword-flows.mdpasswordless-requests.mdusers-collection.mdDetails
From the project READMEMeteor 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.
For a custom users collection, configure both runtimes before app reads or validators. The 3.6 RC fixes collection setup and publication lookup; review collection identity and client-write policy before changing profile rules.
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 = "[email protected]";
});
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. DDP authentication still
reads the resume token into memory; this does not protect it from a malicious
script already executing in the application origin.
With [email protected] (Meteor 3.5.2), the server handles cookie endpoints
only when it is opted in too. A client-only setting can therefore produce an
HTML response or 404 from later handlers. The /set body limit is 4096 bytes;
larger requests return HTTP 413. See
cookie endpoint troubleshooting for the
routes, version boundary, and diagnostic checks. Meteor 3.6-beta.1's
[email protected] also requires trusted origins and valid tokens,
uses SameSite=Strict, and adds a separate per-address endpoint rate limit.
Review custom callers and cross-site entry flows before upgrading. The RC uses
[email protected]; distinguish invalid sessions from transient or
rate-limit responses before retrying or discarding 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. With Meteor 3.6-beta.1's [email protected]
and [email protected], the complete request is validated before
lookup/creation and joins the default Accounts DDP rate rule. See
passwordless request boundaries
for custom callers and earlier packages. The RC pairs
[email protected] with [email protected];
retain the default limiter and handle callback errors without an immediate
retry loop.
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.