Login de usuarios de tu app
Forja guarda las cuentas de las personas que usan tu app (correo y contraseña), les da una sesión y hace que cada una vea solo sus propios registros. Vos armás las pantallas; Forja se ocupa de las contraseñas, las sesiones y los mails.
No confundir. Esto es el login de las personas que usan tu app (tus clientes). El login de quien administra el proyecto en el dashboard de Forja es otra cosa, y las llaves de API (Llaves y permisos) siguen siendo para tu servidor.
Cómo se conecta todo
- Tu app muestra un formulario de registro o de ingreso y llama a Forja con el correo y la contraseña.
- Forja devuelve una sesión: un
accessTokencorto (15 minutos) y unrefreshTokenlargo (30 días). - Tu app manda el
accessTokenen cada pedido a la API de tablas. Forja sabe quién es la persona y le muestra solo lo que las reglas de la tabla permiten.
El navegador de tu usuario habla directo con Forja: no hace falta una llave de API ni un servidor propio.
Antes de empezar: configurar el proyecto
- En el dashboard, abrí Usuarios de la app y encendé el login. Ahí también decidís si se pueden crear cuentas nuevas y si hay que confirmar el correo.
- Cargá los sitios autorizados: las direcciones desde donde se usa tu app (por ejemplo
https://miapp.com), o la palabralocalhostpara probar en tu computadora. Un navegador desde otro sitio recibe un 403. - Si pedís confirmar el correo, indicá a qué página de tu app lleva el enlace del mail (por ejemplo
https://miapp.com/confirmar), y lo mismo para restablecer la contraseña. Tienen que ser de un sitio autorizado. - Para que las personas lean o guarden datos, activá la API automática de la tabla y definí sus reglas de acceso (más abajo). Sin reglas, la tabla es invisible para ellas.
Registro e ingreso
POST https://api.getforja.site/v1/auth/<ID_DEL_PROYECTO>/signup
Content-Type: application/json
{ "email": "ana@ejemplo.com", "password": "una-clave-larga-1234", "metadata": { "nombre": "Ana" } }Si el proyecto no pide confirmar el correo, la respuesta ya es una sesión:
{
"accessToken": "eyJ…",
"tokenType": "bearer",
"expiresIn": 900,
"expiresAt": "2026-10-31T12:15:00.000Z",
"refreshToken": "q1w2e3…",
"user": { "id": "…", "email": "ana@ejemplo.com", "emailVerified": false, "metadata": { "nombre": "Ana" }, "createdAt": "…" }
}Si sí lo pide, la cuenta se crea sin sesión y se manda un mail con el enlace:
{ "verificationRequired": true }El campo metadata es opcional: un objeto JSON con datos de la persona (nombre, preferencias…), de hasta 4096 bytes. El ingreso es igual, con POST …/login y los mismos dos campos.
const API = "https://api.getforja.site";
const PROYECTO = "<ID_DEL_PROYECTO>";
async function auth(ruta, cuerpo, accessToken) {
const res = await fetch(API + "/v1/auth/" + PROYECTO + "/" + ruta, {
method: cuerpo === undefined && accessToken ? "GET" : "POST",
headers: {
...(cuerpo !== undefined ? { "Content-Type": "application/json" } : {}),
...(accessToken ? { Authorization: "Bearer " + accessToken } : {}),
},
body: cuerpo !== undefined ? JSON.stringify(cuerpo) : undefined,
});
const datos = res.status === 204 ? null : await res.json();
if (!res.ok) throw Object.assign(new Error(datos.error), { code: datos.code, status: res.status });
return datos;
}
// Registro
const alta = await auth("signup", { email, password });
if (alta.verificationRequired) {
// Mostrá "Revisá tu correo": todavía no hay sesión.
} else {
guardarSesion(alta); // ya entró
}
// Ingreso
const sesion = await auth("login", { email, password });
guardarSesion(sesion);
// Datos de la persona que inició sesión
const yo = await auth("me", undefined, sesion.accessToken);Confirmar el correo y recuperar la contraseña
Los mails los manda Forja desde no-responder@getforja.site, en español y con el nombre de tu proyecto. El botón lleva a la página de tu app que configuraste, con un ?token=… en la dirección. Esa página lee el código y se lo manda a Forja:
// Página /confirmar de tu app (la que cargaste en "Enlace de verificación")
const token = new URLSearchParams(location.search).get("token");
await auth("verify-email", { token });
// Mostrá "Correo confirmado" y llevá a la persona a iniciar sesión.
// Página /nueva-clave de tu app (la que cargaste en "Enlace de restablecer contraseña")
const token = new URLSearchParams(location.search).get("token");
await auth("reset-password", { token, password: nuevaClave });- El enlace de confirmación vence a las 24 horas; el de contraseña, a la 1 hora. Los dos sirven una sola vez.
- Confirmar el correo no inicia sesión: la persona entra después con su correo y contraseña. Es a propósito, para que nadie deje una sesión abierta en la cuenta de otro.
- “Olvidé mi contraseña” responde lo mismo exista o no la cuenta, para no revelar quién está registrado. Cambiar la contraseña cierra todas las sesiones abiertas.
- Si el enlace venció,
POST …/resend-verificationoPOST …/forgot-passwordmandan uno nuevo.
Mantener la sesión
// Cuando una llamada devuelve 401, o antes de que venza expiresAt:
const nueva = await auth("refresh", { refreshToken: sesion.refreshToken });
guardarSesion(nueva); // el refreshToken anterior ya no sirve: guardá el nuevo- Cada renovación entrega un
refreshTokennuevo y deja sin efecto el anterior. - Si alguien usa un
refreshTokenque ya se usó (por ejemplo, porque lo copiaron), Forja cierra toda esa sesión y la persona tiene que volver a ingresar. POST …/logoutcon elrefreshTokencierra esa sesión;POST …/logout-all(con elaccessToken) cierra todas y deja sin efecto losaccessTokenvigentes al instante.- Guardá el
refreshTokencon cuidado: quien lo tenga puede entrar como esa persona durante 30 días. Cualquier dato guardado en el navegador puede leerlo un script malicioso de tu página, así que no cargues código de terceros que no controles.
Que cada persona vea solo lo suyo
En la tabla creás una columna que guarda a quién pertenece cada registro (texto o UUID) y, en Acceso de los usuarios de tu app, elegís qué puede hacer la persona que inició sesión:
| Regla | Opciones |
|---|---|
| Ver | solo sus registros, todos los de la tabla, o ninguno |
| Crear | sí (queda a su nombre) o no |
| Editar | solo los suyos, o no |
| Borrar | solo los suyos, o no |
GET https://api.getforja.site/v1/tables/<ID_DEL_PROYECTO>/notas
Authorization: Bearer <accessToken de la persona>
POST https://api.getforja.site/v1/tables/<ID_DEL_PROYECTO>/notas
Authorization: Bearer <accessToken de la persona>
Content-Type: application/json
{ "texto": "Comprar pan" }- El dueño se completa solo con el id de la persona al crear. No hace falta mandarlo, y si intenta mandar el de otra persona, Forja lo rechaza (403).
- El dueño de un registro no se puede cambiar. Un registro ajeno responde 404, igual que uno que no existe.
- El filtro por dueño se aplica dentro de la consulta, así que no hay manera de saltearlo con parámetros de la URL (
filter[…]no puede pedir los registros de otra persona). - Una tabla sin reglas no existe para ellas: responde 404.
Cuidado con “todos los registros”. Si dejás que las personas vean todos los registros y además permitís que cualquiera se registre, la tabla queda pública para quien cree una cuenta. El dashboard te lo advierte; usalo solo para datos que de verdad pueda ver todo el mundo.
Referencia de rutas
Todas cuelgan de https://api.getforja.site/v1/auth/<ID_DEL_PROYECTO>.
| Ruta | Qué hace |
|---|---|
POST /signup | Crea la cuenta. Devuelve una sesión, o verificationRequired si hay que confirmar el correo. |
POST /login | Inicia sesión con correo y contraseña. |
POST /refresh | Cambia un refreshToken por una sesión nueva. |
POST /logout | Cierra la sesión del refreshToken enviado (responde 204). |
POST /logout-all | Cierra todas las sesiones de la persona (con su accessToken; responde 204). |
GET /me | Los datos de la persona que inició sesión. |
POST /change-password | Cambia la contraseña con currentPassword y newPassword; devuelve una sesión nueva. |
POST /verify-email | Confirma el correo con el token del mail. |
POST /resend-verification | Manda otro enlace de confirmación (responde 202 exista o no la cuenta). |
POST /forgot-password | Manda el enlace para elegir una contraseña nueva (responde 202 exista o no la cuenta). |
POST /reset-password | Elige una contraseña nueva con el token del mail. |
Errores
Los errores traen un texto en español en error y, cuando tu app tiene que decidir algo, un code estable.
| Código HTTP | code | Cuándo |
|---|---|---|
| 400 | WEAK_PASSWORD, INVALID_METADATA, INVALID_TOKEN | Contraseña que no sirve, metadata inválida, o enlace de mail vencido o ya usado. |
| 401 | INVALID_CREDENTIALS, UNAUTHENTICATED | Correo o contraseña incorrectos (siempre el mismo mensaje), o falta iniciar sesión. |
| 403 | EMAIL_NOT_VERIFIED, ACCOUNT_DISABLED, SIGNUPS_DISABLED, ORIGIN_NOT_ALLOWED | Falta confirmar el correo, cuenta deshabilitada, altas cerradas, o el sitio no está autorizado. |
| 404 | AUTH_NOT_ENABLED | El proyecto no existe o no tiene el login encendido. |
| 409 | EMAIL_TAKEN | Ya hay una cuenta con ese correo. |
| 429 | RATE_LIMITED, ACCOUNT_LOCKED, EMAIL_LIMIT_REACHED | Demasiados intentos, cuenta bloqueada unos minutos, o se llegó al tope diario de mails. |
Límites y protecciones
- La contraseña tiene entre 8 y 128 caracteres, y no puede ser igual al correo ni un solo carácter repetido.
- Después de 10 intentos fallidos la cuenta se bloquea 15 minutos.
- Los pedidos que mandan un mail (alta, reenviar confirmación, olvidé mi contraseña) están limitados a 3 cada 10 minutos por correo.
- Tu plan define cuántas cuentas puede tener cada proyecto y cuántos mails de login (confirmación y recuperación) se mandan por día: Free, hasta 500 cuentas y 20 mails por día; Starter, 10.000 cuentas y 300 mails; Growth, 50.000 cuentas y 1.500 mails; Enterprise, a medida. Los precios están en Precios. Los usuarios de tu app nunca ven nada de tu plan: solo reciben un mensaje genérico.
- Las contraseñas se guardan con
scrypty nunca se pueden leer, ni siquiera vos.
Qué no incluye todavía
- Ingreso con Google u otros proveedores para tus usuarios (hoy es solo correo y contraseña).
- Verificación en dos pasos y enlaces mágicos.
- Usar la sesión de una persona en las funciones, en pagos o en el servidor MCP: esas siguen usando llaves de API.
- Datos de staging: las cuentas de las personas son solo de producción.
- Roles de usuario (administrador, editor…), mails con tu propio remitente o diseño, e importar cuentas de otro sistema.
- Exportar las cuentas: la exportación del proyecto trae tus tablas y funciones, pero no los correos ni las contraseñas de tus usuarios. Si algún día migrás, tendrán que volver a registrarse o crear una contraseña nueva.
Esta lista y otras están en Qué no hace todavía.
Información revisada el 2026-10-31.