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.
- Register creates the
_usersrow withemailVerified: falseand sends a verification email. It does not return a JWT. You cannot forceemailVerifiedin the request body. - The user clicks the Tefily link. The hosted page (or your app) calls verify-email. Tokens last 24 hours and are single-use.
- Login is allowed only after the address is confirmed. It returns a project JWT (24 hours). The client stores it and sends
Authorization: Beareron later requests. - 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();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');print(client.token);
final me = await client.auth.me();client.auth.register( email="jane@example.com", password="secret123", extra_fields={"name": "Jane"},)# After the user clicks the Tefily verification link:client.auth.login(email="jane@example.com", password="secret123")print(client.token)
me = 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.
What this auth does not do
Section titled “What this auth does not do”- 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/mewith a bad or non-project token is401Invalid 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, andemailVerifiedare validated against non-reserved_usersfields when any extra key is present.emailVerifiedin the body is removed and stored asfalse. The server does not applydefaultValueon 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, orusernameis 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_userswhen registration should succeed with the client sending them.extraFields/extra_fieldsare 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-emailor/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.
Emails
Section titled “Emails”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.
| 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.
Daily email cap
Section titled “Daily email cap”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.
Access rules
Section titled “Access rules”After auth is on, put requireAuth (and owner compares) on every collection method your app uses. See Access rules.
Verify on your backend
Section titled “Verify on your backend”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? }.
Mint a JWT from the console
Section titled “Mint a JWT from the console”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.