Praxsuite

Autenticación de usuarios finales

Vincent Depassier · 17 de septiembre de 2026

Estas rutas autentican a los usuarios de tu aplicación — no a las personas que entran a Praxsuite. Emiten un JWT que tu cliente después manda en /query y /schema, donde los roles del usuario final deciden qué filas y qué columnas vuelven.

Auth: una API key, pk_live_ o sk_live_. Un JWT de usuario final se rechaza aquí — la única excepción es change-password, que exige el JWT en su lugar. Tres rutas no piden credencial: config, logo y jwks.json.

El sobre de respuesta

Todas las rutas de esta página salvo config, logo y confirm-email responden en el sobre de servicio:

{
  "isSuccess": true,
  "statusCode": 200,
  "message": null,
  "data": { },
  "errors": []
}

Al fallar, isSuccess es false, data es null, y el motivo está en message — con detalle por campo en errors cuando lo que falló fue la validación. El estado HTTP coincide con statusCode.

Arranque

GET /{workspaceId}/auth/config

Pública, sin credencial. Así es como una app de navegador se entera de qué clave usar.

{
  "success": true,
  "publicKey": "pk_live_…",
  "branding": {
    "name": "Acme",
    "lightPrimary": "…", "darkPrimary": "…",
    "hasLogo": true,
    "logoUrl": "/api/v1/gateway/{workspaceId}/auth/logo"
  },
  "authPageConfig": {
    "defaultLanguage": "es",
    "enabledRegisterFields": ["firstName", "lastName"],
    "enabledSocialProviders": ["google"],
    "requireEmailConfirmation": false,
    "termsUrl": null,
    "privacyUrl": null
  },
  "providers": [ ],
  "oidcProviders": [ ]
}

publicKey se entrega sólo para una credencial que alguien haya marcado como publicable. Si nadie lo hizo, esta ruta responde 404 y lo explica: publicar una clave le da sus permisos de tabla a cualquiera que tenga el GUID del workspace, así que es un acto deliberado y no un valor por defecto. Acota la credencial primero.

providers es la lista completa de proveedores de identidad que acepta este workspace, cada uno con su tipo. enabledSocialProviders y oidcProviders son vistas más viejas y angostas de las mismas filas, mantenidas para los clientes que ya las leen.

GET /{workspaceId}/auth/logo

Pública. Redirige al logo del workspace, cacheable una hora. 404 si el workspace no tiene.

GET /{workspaceId}/auth/jwks.json

Pública. La clave pública RSA del workspace en formato JWKS, para verificar tokens de redirección post-login (RS256) sin secreto compartido. Cacheable una hora.

Registro e inicio de sesión locales

POST /{workspaceId}/auth/register
{ "email": "a@b.com", "password": "minimo-8", "firstName": "Ada", "lastName": "Lovelace", "username": "ada" }

email y password son obligatorios; la contraseña va de 8 a 128 caracteres. El resto es opcional, y cuáles de esos campos debería mostrar tu formulario está en authPageConfig.enabledRegisterFields.

POST /{workspaceId}/auth/login
{ "email": "a@b.com", "password": "…" }

Las dos rutas devuelven el mismo data:

{
  "accessToken": "eyJ…",
  "refreshToken": "…",
  "accessTokenExpiresAt": "2026-01-14T10:22:11Z",
  "refreshTokenExpiresAt": "2026-02-13T09:22:11Z",
  "tokenType": "Bearer",
  "user": {
    "id": "…",
    "email": "a@b.com",
    "firstName": "Ada",
    "lastName": "Lovelace",
    "username": "ada",
    "roles": ["cliente"]
  },
  "requiresEmailConfirmation": false,
  "emailVerified": true,
  "postLoginRedirectUrl": null
}

Cuando el workspace exige confirmación de correo, un registro devuelve requiresEmailConfirmation: true con endUserId y email, y sin tokens. No lo trates como una llamada fallida.

Si el workspace tiene un proveedor OIDC configurado, login valida las credenciales contra ese proveedor en vez de localmente. Sin redirección, y sin que cambie nada para tu cliente.

POST /{workspaceId}/auth/refresh
POST /{workspaceId}/auth/logout

Las dos toman { "refreshToken": "…" }. Refresh devuelve un par nuevo e invalida el refresh token anterior — rotan, así que guarda sólo el último. Logout revoca un refresh token, lo que cierra la sesión en un dispositivo.

Confirmación de correo

GET /{workspaceId}/auth/confirm-email?token=…
POST /{workspaceId}/auth/resend-confirmation      { "email": "a@b.com" }

confirm-email es pública y devuelve una página HTML con el branding del workspace, no JSON: se abre desde un enlace de correo, que no puede mandar cabeceras.

Contraseña

POST /{workspaceId}/auth/forgot-password     { "email": "a@b.com" }
POST /{workspaceId}/auth/verify-reset-code   { "email": "a@b.com", "code": "123456" }
POST /{workspaceId}/auth/reset-password      { "sessionToken": "…", "newPassword": "…", "confirmPassword": "…" }
POST /{workspaceId}/auth/change-password     { "currentPassword": "…", "newPassword": "…", "confirmPassword": "…" }

El flujo de recuperación son tres llamadas: pedir un código, canjear el código de seis dígitos por un sessionToken de vida corta, y usar ese token para fijar la contraseña nueva. Las contraseñas nuevas van de 8 a 128 caracteres.

change-password es la única ruta de esta página que toma el JWT del usuario final en vez de una API key — el usuario está cambiando su propia contraseña, así que es su identidad la que hay que demostrar.

Inicio de sesión social y federado

GET  /{workspaceId}/auth/{providerSlug}/authorize
POST /{workspaceId}/auth/{providerSlug}/callback   { "code": "…", "state": "…", "redirectUri": "…" }

authorize devuelve la URL a la que mandar el navegador. callback canjea el código devuelto por un JWT del gateway, en el mismo data que login. state es de un solo uso y se verifica contra CSRF, y redirectUri tiene que coincidir con el que se usó al autorizar.

authorize resuelve qué hacer según el tipo del proveedor: para un proveedor de navegador devuelve una URL; para uno de aserción de servidor responde 400, porque no hay ninguna página a la que mandar a nadie.

La forma vieja, sólo para OIDC, sigue funcionando y significa lo mismo:

GET  /{workspaceId}/auth/oidc/{providerSlug}
POST /{workspaceId}/auth/oidc/callback   { "providerSlug": "…", "code": "…", "state": "…", "redirectUri": "…" }

Un segmento literal de ruta le gana al parámetro, así que /auth/oidc/... sigue llegando al handler viejo.

Autenticar a un jugador desde el servidor del juego

POST /{workspaceId}/auth/{providerSlug}/assert
{
  "platformPlayerId": "1234567",
  "displayName": "ada",
  "avatarUrl": "https://…",
  "metadata": { "accountAge": 812, "region": "us-east" }
}

Dentro de un juego no hay navegador, así que no hay redirección posible. Tu servidor de juego responde por el jugador, y lo que se confía aquí es tu clave sk_live_ — que tiene que ser una clave de servidor marcada para esa plataforma. Lee platformPlayerId del lado del servidor, desde la API de la plataforma; un valor que te mandó el cliente es un valor que el jugador puede falsificar.

displayName y avatarUrl se guardan para rankings y vistas de administración, y no se confían para nada.

Devuelve el mismo data que login.

Errores

Estado

Causa típica

400

Falló la validación, o el proveedor no hace login de navegador

401

Sin API key, o un JWT donde se exige una API key

404

No hay credencial publicable, no existe el proveedor, o no hay logo

409

El correo ya está registrado

501

El tipo de proveedor está registrado pero su camino de login todavía no está construido

Toda llamada de aquí se escribe en el log de consultas como evento Auth, con la acción, el correo, el estado y la IP de origen.