External Identity
Los end-users de tu app (portal de clientes, ecommerce, app móvil, dispositivo IoT) se pueden
registrar y autenticar directamente contra Nxar, sin que tu backend tenga que armar su propia capa
de auth. Cada user queda como un row en tu entity Users con userType='external', y obtiene un
JWT propio para hacer requests al Public API.
¿Te confundís con Public API? El Public API expone tus records. External Identity expone los usuarios de tu app. Se complementan: tu app del cliente usa External Identity para login, después usa el JWT resultante contra el Public API para ver/editar records.
Cuándo usar External Identity
✅ Tenés un portal de clientes / app móvil / ecommerce y querés:
- Que tus end-users hagan login con email + password.
- Que cada uno solo vea sus propios records (sharing por record).
- Recuperación de password sin intervención del admin.
❌ No es para:
- Integraciones server-to-server (eso es Public API con API key).
- Empleados de tu equipo que entran al admin de Nxar (eso es el login web normal).
- Single Sign-On con Google/Apple (M1 solo email + password — magic link y OAuth = follow-up).
Quickstart
1. Habilitar External Identity en tu workspace
Desde el admin de Nxar: Configuración → Acceso y seguridad → Usuarios externos.
| Opción | Descripción |
|---|---|
| Habilitar External Identity | Master toggle. OFF por default — prender solo cuando estés listo. |
| Permitir registro abierto | Si está OFF, el endpoint /auth/register devuelve 403 (futuro: invite-only). |
| Modo de activación de email | nxar_email (Nxar manda el verify email) / app_managed (tu app activa via API) / none (sin verificación). |
| Permission Set por defecto | PS que se asigna automáticamente al user al registrarse. Sólo le da acceso a entidades: un external user nunca recibe permisos de sistema, aunque el PS los tenga. |
| Email de bienvenida post-activación | Si querés un segundo email después del verify. |
Necesitás el permiso tenant_settings (viene en System Admin).
Qué puede hacer un external user
Un external user nunca recibe system permissions (manage_users, login_as,
tenant_settings, etc.) ni all_entity_access, aunque un admin le asigne una PS que los
otorgue. Sólo cuenta el acceso por entidad y por campo de sus PS, y siempre pasa por el sharing
por record: ve los records que le pertenecen o que se le compartieron explícitamente. Para
segmentación por equipos o por cliente, la herramienta es role hierarchy + sharing rules.
Si tu portal lo usa tu propio staff para administrar, dales usuarios internos y que entren por el web UI de Nxar.
2. Definir qué campos guardás de cada user
Si querés capturar campos extra al registro (teléfono, empresa, idioma, etc.), declarálos como
custom fields en la entity Users:
Configuración → Constructor de app → Entidades → Users → Campos → Nuevo.
El handler de register acepta cualquier key en el body. Cada key se evalúa así:
| Caso | Comportamiento |
|---|---|
Es columna hard de users (email, password, name, locale) | Va a la columna. |
| Es campo declarado en el schema de Users (custom field) | Va a users.custom_data JSONB. |
| No está declarado en el schema | Se ignora silenciosamente. |
Validación rich de tipos / regex / required queda para M2. M1 acepta strings sin validar per-field — chequealo en tu backend antes de mandar.
3. Tu primer registro
curl -X POST https://tu-tenant.nx-ar.com/public/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "supersegura123",
"name": "Alice García",
"phone": "+54 11 5555-1234",
"company": "Acme SA"
}'Response:
{
"data": {
"userId": "...",
"activationMode": "nxar_email"
},
"meta": {}
}Si el activation mode es app_managed, también recibís activationToken que después usás en
POST /auth/users/:id/activate con un PAT que tenga can_manage_external_users.
4. Login
curl -X POST https://tu-tenant.nx-ar.com/public/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"supersegura123"}'Response:
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "abc...xyz",
"expiresIn": 3600
},
"meta": {}
}accessToken: JWT HS256, 1h. Va en el headerAuthorization: Bearer ...de cada request al Public API.refreshToken: opaque random, 30d. Solo se usa contra/auth/refresh. Guardalo en storage seguro (httpOnly cookie ideal, AsyncStorage en mobile, etc.).
5. Hacer un request al Public API
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://tu-tenant.nx-ar.com/public/v1/data/ordersEl user solo va a ver records sobre los que tiene visibilidad según el sharing por record (ver Sharing si necesitás cambiar el OWD de la entidad).
6. Refresh cuando vence el access token
Después de ~1h el access token expira. Refrescalo:
curl -X POST https://tu-tenant.nx-ar.com/public/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"abc...xyz"}'Recibís un nuevo access token + nuevo refresh token. El refresh anterior queda revocado (rotation forzada). Si reusás un refresh ya rotado, Nxar detecta el reuse y revoca toda la cadena de tokens — el user tiene que loginear de nuevo. Esto es protección anti-theft estándar.
Race tolerance: dos tabs concurrentes refrescando con el mismo token dentro de una ventana de 30s reciben el mismo nuevo token (no revoca). Más allá de 30s, asume theft y revoca.
Endpoints
| Method + Path | Body | Auth |
|---|---|---|
POST /public/v1/auth/register | {email, password, name, ...customFields} | Anonymous |
POST /public/v1/auth/verify | {token} | Anonymous (token = secret) |
POST /public/v1/auth/users/:id/activate | {activationToken} | PAT con can_manage_external_users |
POST /public/v1/auth/login | {email, password} | Anonymous |
POST /public/v1/auth/refresh | {refreshToken} | Anonymous (refresh = secret) |
POST /public/v1/auth/logout | {refreshToken} | Anonymous (refresh = secret) |
POST /public/v1/auth/request-password-reset | {email} | Anonymous — siempre 200 |
POST /public/v1/auth/password-reset | {token, newPassword} | Anonymous (token = secret) |
GET /public/v1/auth/me | — | External JWT |
Todos gated por el master toggle. Si External Identity está OFF, todos responden 503 SERVICE_UNAVAILABLE.
Modos de activación
Cuando un user se registra, Nxar tiene que verificar que el email es real (o no). Tres modos:
nxar_email (default — más seguro)
Nxar manda automáticamente el email de verificación con un link /verify?token=.... El user lo
clickea, tu app llama POST /auth/verify con el token, queda activo. Usa el template
external-user-email-verification que podés editar en Configuración → Email → Contenido → Email Templates.
app_managed
Tu backend recibe el activationToken en la response del register. Activa cuando quieras (después
de un OTP por SMS, después de aprobación manual, etc.) llamando:
curl -X POST https://tu-tenant.nx-ar.com/public/v1/auth/users/{userId}/activate \
-H "Authorization: Bearer nxar_api_..." \
-H "Content-Type: application/json" \
-d '{"activationToken":"<el token de register>"}'El PAT necesita el system permission can_manage_external_users. Mantenelo bien protegido —
quien tenga este PAT puede activar cualquier user.
none (no recomendado)
El user queda activo de inmediato sin verificación. Solo para entornos cerrados o testing — abre superficie a spam/abuse.
Rate limits
Para protegerte de brute-force y spam:
| Endpoint | Limit |
|---|---|
/auth/register | 5/IP/hora + 30/tenant/hora |
/auth/login | 10/IP/min + 5/email/15min (bloquea 30min al exceder) |
/auth/request-password-reset | 3/email/hora + 30/tenant/hora |
/auth/refresh | 60/token/min |
/auth/verify, /auth/password-reset | 30/IP/min |
Cuando excedés, recibís 429 TOO_MANY_REQUESTS con header Retry-After (en segundos).
Seguridad — qué hace Nxar por vos
- Anti-enumeration en login: el error es genérico (
INVALID_CREDENTIALS) tanto si el email no existe como si la password es mala. Comparamos contra un bcrypt dummy hash cuando el email no existe para mantener timing constante. - Anti-enumeration en request-password-reset: siempre
200 {success: true}, mandemos email o no. Tu app no puede usar este endpoint para sondear qué emails están registrados. - Email único globalmente en el tenant: si el email ya está en uso (sea internal o external),
el error es explícito (
EMAIL_TAKEN) con un mensaje que ayuda al user (login en vez de signup, o contactar al admin si está como internal). - Refresh tokens hash-stored: solo el SHA-256 del token se guarda en DB (
refresh_tokens.token_hash). Si la DB se filtra, los tokens raw no se pueden derivar. - Theft detection: chain de
parent_token_idenrefresh_tokens. Reuso de un refresh ya rotado revoca toda la cadena. - Defense in depth en el admin: los external users nunca pueden entrar al admin web de Nxar
(login web los rechaza por
userTypeywithAuthrechaza el JWT portyp).
Edge cases
Email ya registrado
{
"error": {
"code": "EMAIL_TAKEN",
"message": "Este email ya está registrado. Probá iniciar sesión."
}
}Si el email pertenece a un internal user (empleado del tenant), el mensaje cambia para sugerir contactar al admin.
Password reset de un user sin verificar
Silenciosamente no manda nada — devuelve 200 {success: true} igual. Solo procesamos reset si
el user existe + es external + activo + verified.
Tokens vencidos
- Access token vencido → response
401 UNAUTHORIZED. Llamá/auth/refresh. - Refresh token vencido (30d) →
/auth/refreshdevuelve 401. El user tiene que loginear de nuevo. - Activation token vencido (24h) →
/auth/verifydevuelve400 TOKEN_EXPIRED. Reenvía pidiéndole al user que se registre de nuevo o pedile al admin que dispareforce-password-resetdesde el detail del user.
Deactivate del admin revoca refresh tokens
Si el admin desactiva un external user desde Acceso y seguridad → Usuarios externos, Nxar revoca automáticamente todos sus refresh tokens activos. El access token actual sigue válido hasta los próximos 60min (vida útil del JWT), pero el siguiente refresh fallará.
Customizar los emails
Los 3 templates de auth viven en Configuración → Email → Contenido → Email Templates:
external-user-email-verification— link de verifyexternal-user-password-reset— link de resetexternal-user-welcome— opcional, opt-in
Variables disponibles: {{name}}, {{verify_url}}, {{reset_url}}, {{app_url}}, {{tenant_name}}.
URLs apuntan al subdomain de tu workspace (https://tu-tenant.nx-ar.com/verify?token=... etc.).
Estos paths no existen en Nxar como UI hosted — tu app implementa las páginas /verify,
/reset-password, / y llama los endpoints del API correspondientes. (Futuro Hosted Auth los va
a servir por default si no tenés UI propia.)
Limitaciones de M1
- Solo email + password. OAuth providers (Google, Apple), magic link / passwordless = follow-up.
- MFA para external users no implementado.
- Hosted signup/login UI con tu branding y custom domain = follow-up. Hoy tu app arma su propia UI.
- Custom outbound domain con DKIM/SPF (que los emails parezcan venir 100% de tu dominio sin trace de Nxar) = follow-up.
- Validation rich de custom fields al register (type / regex / required) = M2. M1 acepta cualquier string.
- “List my refresh tokens” / “revoke specific session” desde el end-user no expuesto. Solo
/auth/logout(revoca el actual) o el admin desde Settings → Users.
Más info
- Para detalles internos de implementación (tokens, pipeline y tablas), contactá al equipo de Nxar.