Skip to content

End-user auth

Project auth is opt-in. Toggle it in Database (or POST /api/projects/:projectId/auth/toggle). Tefily creates a _users collection with email, password, and emailVerified.

This is not the same as signing in to app.tefily.com. Console users have platform JWTs. App users have project JWTs (type: project). REST shapes, emails, and token lifetimes: Dynamic project API.

  1. Register creates the _users row with emailVerified: false and sends a verification email. It does not return a JWT. You cannot force emailVerified in the request body.
  2. The user clicks the Tefily link. The hosted page (or your app) calls verify-email. Tokens last 24 hours and are single-use.
  3. Login is allowed only after the address is confirmed. It returns a project JWT (24 hours). The client stores it and sends Authorization: Bearer on later requests.
  4. Password reset is a second email flow: request → link (1 hour) → complete → login with the new password. Complete does not return a JWT.

Register, login, and password reset can fire webhooks (auth.registered, auth.logged_in, auth.password_reset.*).

await client.auth.register({
email: 'jane@example.com',
password: 'secret123',
extraFields: { name: 'Jane' },
});
// After the user clicks the Tefily verification link:
await client.auth.login({ email: 'jane@example.com', password: 'secret123' });
console.log(client.token);
const me = await client.auth.me();

Existing _users rows without emailVerified are treated as verified so apps that already have users keep working.

Passwords must be at least 8 characters (register also caps at 72). Email is stored lowercase.

  • There is no refresh route and no refresh token. Login returns { "token", "user" }. The JWT lifetime is 24 hours. After expiry the client calls login again.
  • There is no logout route. Discard the token on the client. GET /auth/me with a bad or non-project token is 401 Invalid or expired token.
  • There is no OAuth, SSO, or social login for project users. The mounted auth routes are email and password. Project users do not use platform OAuth.
  • Extra profile fields: keys other than email, password, and emailVerified are validated against non-reserved _users fields when any extra key is present. emailVerified in the body is removed and stored as false. The server does not apply defaultValue on those extra fields. Required extra fields are checked only when that extra data is non-empty, so a bare { "email", "password" } register does not run those required-field checks.
  • Honeypot on register and login: if any of website, url, homepage, fax, company, or username is present and non-empty, the API records a hit and returns a success body without creating a user or issuing a JWT. Empty trap keys are stripped. Skip those names on _users when registration should succeed with the client sending them. extraFields / extra_fields are spread into the JSON body, so a field with one of those names trips the honeypot.
  • Email links are hash routes on https://app.tefily.com: {base}/#{path}?projectId=…&token=… with path /auth/verify-email or /auth/reset-password. The app cannot change the link host. Password-reset complete checks that the password length is at least 8. Register is the flow that also caps at 72.

Tefily sends the mail and hosts the landing pages on the console app (PUBLIC_APP_BASE_URL, hosted: https://app.tefily.com). APP_NAME in the templates is the project name. The app cannot change the link host.

Email Trigger Link
Verify address Register and resend {app}/#/auth/verify-email?projectId=…&token=…
Reset password Reset request {app}/#/auth/reset-password?projectId=…&token=…
Password changed After a successful reset none (notification only)
await client.auth.resendVerification('jane@example.com');
await client.auth.verifyEmail(token);
await client.auth.requestPasswordReset('jane@example.com');
await client.auth.completePasswordReset({ token, password: 'newsecret' });

resend-verification and password-reset/request always return { sent: true } so they do not reveal whether the email exists. A new token invalidates unused tokens of the same kind.

Verification and password-reset emails count against your plan’s end-user auth emails per day (UTC day): 100 on Free, 2,000 on Pro, 10,000 on Scale. Once a project reaches the cap, further requests that day still return the same { sent: true } response but no email is sent, so the endpoints do not reveal whether the cap or an account exists. The count resets at 00:00 UTC. Billing and operational emails to your own team do not count toward this cap.

POST /auth/verify-email confirms the address. POST /auth/verify is different: it is how your server checks a project JWT with a service token.

Method Path Auth
POST /api/p/:projectId/auth/register none
POST /api/p/:projectId/auth/login none
GET /api/p/:projectId/auth/me project JWT
POST /api/p/:projectId/auth/resend-verification none
POST /api/p/:projectId/auth/verify-email none
POST /api/p/:projectId/auth/password-reset/request none
POST /api/p/:projectId/auth/password-reset/complete none
POST /api/p/:projectId/auth/verify service token

Register and login are public when auth is enabled. me requires a project JWT. Full request and response bodies: Dynamic project API.

After auth is on, put requireAuth (and owner compares) on every collection method your app uses. See Access rules.

Your server should not trust a project JWT without checking it. Use a service token and POST /api/p/:projectId/auth/verify with { "token": "<project jwt>" }. The response is { valid, user? }.

Admins can mint a project-user JWT (POST /api/projects/:projectId/users/:userId/token) for the API tester. Do not use that as a substitute for login in production apps.