Esta página aún no está traducida: se muestra la versión en inglés.
Auth & Players
El inicio de sesión es un paso que puedes sobrescribir, no una caja negra. Proveedores, sesiones, vinculación de identidades, baneos. Cada punto del flujo — antes y después del inicio de sesión, antes y después de vincular, antes y después de una fusión, al cambiar un estado — es un punto de extensión declarado, con un tipo declarado: un filtro, que puede rechazar el paso, o un observador, que no puede.
Cuándo usarlo
- Los jugadores (player) tienen que iniciar sesión con el dispositivo, email, Apple, Google, Steam o un proveedor propio. Crear al jugador en su primer inicio de sesión (sign-in) se activa con un flag, no con un segundo flujo.
- Una cuenta de invitado tiene que poder convertirse más adelante en una cuenta completa:
Linkagrega Steam sin perder el progreso, y una fusión (merge) reúne dos cuentas en un solo jugador. - Las políticas tienen que ejecutarse donde nadie pueda saltárselas: un filtro por región antes del inicio de sesión, un kit de inicio después del inicio de sesión que creó al jugador.
- La moderación tiene que actuar de inmediato: revocar sesiones, suspender, banear un dispositivo, con un evento
bannedque escuchan todos los sistemas en ejecución. - El contexto declarado (región, plataforma, build) tiene que llegar a todos los hooks posteriores sin que cada uno vuelva a leer al jugador para averiguarlo.
- No hay una alternativa más liviana. Todos los demás módulos identifican a quien los llama a través de este, y
authno se puede desactivar mientras alguno de ellos necesite un actor jugador: el configurador de módulos se niega y enumera los módulos que dependen de él.
Quién hace qué
| Actor | Qué hace en esta página |
|---|---|
player | inicia sesión, vincula o desvincula identidades, renueva sus credenciales, cierra sesión |
moderator | revoca sesiones; banea, suspende o rehabilita jugadores |
backend-service | filtra el inicio de sesi ón por región; crea las primeras filas de un jugador nuevo; lee y revoca sesiones |
En resumen
SignIn por proveedor y un flag para crear al jugador en su primer inicio de sesión; Link agrega Steam// client — one call per provider; create-on-first-sign-in is a flag
var session = await PlayServ.Auth.SignIn(Provider.Device, create: true);
await PlayServ.Auth.Link(Provider.Steam); // one player, many identities// client — one call per provider; create-on-first-sign-in is a flag
const session = await PlayServ.auth.signIn(Provider.Device, { create: true });
await PlayServ.auth.link(Provider.Steam); // one player, many identities# client — one call per provider; create-on-first-sign-in is a flag
session = await playserv.auth.sign_in(Provider.DEVICE, create=True)
await playserv.auth.link(Provider.STEAM) # one player, many identitiesTodavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
// client — one call per provider; create-on-first-sign-in is a flag
Client->Auth->SignInWithProvider(FPSProviderId::Device, Credential,
TPSOnResult<FPSSession>::CreateWeakLambda(this, [this](const TPSResult<FPSSession>& Result)
{
if (!Result.HasValue()) { return; }
// one player, many identities — add Steam to the same account
Client->Auth->Providers->Link(FPSProviderId::Steam, SteamCredential);
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.// client — one call per provider; create-on-first-sign-in is a flag
var session = await PlayServ.Auth.SignIn(Provider.Device, create: true);
await PlayServ.Auth.Link(Provider.Steam); // one player, many identitiesCada punto se personaliza en el mismo lugar donde se declara. Todas las formas que puede tomar un handler están reunidas en Extensibility:
[Before(Auth.SignIn)] // a gate: it may refuse, and it is fail-closed
public static Verdict GateRegion(SignInAttempt a) =>
a.Region == "sanctioned"
? Hook.Reject(Problem.Forbidden, "region not served")
: Hook.Continue(a);
[After(Auth.SignIn, created: true)] // an observer: it watches, it cannot refuse
public static async Task GrantStarterPack(Player player)
{
await player.Inventory.Grant("chest.gold", count: 1);
}// a gate: it may refuse, and it is fail-closed
export const gateRegion = before(Auth.signIn, (a: SignInAttempt) =>
a.region === 'sanctioned'
? Hook.reject(Problem.forbidden, 'region not served')
: Hook.continue(a));
// an observer: it watches, it cannot refuse
export const grantStarterPack = after(Auth.signIn, { created: true },
async (player: Player) => {
await player.inventory.grant('chest.gold', { count: 1 });
});@before(auth.sign_in) # a gate: it may refuse, and it is fail-closed
def gate_region(a: SignInAttempt) -> Verdict:
if a.region == "sanctioned":
return Hook.reject(Problem.FORBIDDEN, "region not served")
return Hook.continue_(a)
@after(auth.sign_in, created=True) # an observer: it watches, it cannot refuse
async def grant_starter_pack(player: Player):
await player.inventory.grant("chest.gold", count=1)Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
Tanto una sobrescritura como un hook son funciones en la nube: se ejecutan en la plataforma, no en el motor. Escríbelas en C#, TypeScript o Python; Unreal se suscribe a los eventos que generan. Están previstas funciones escritas en el propio motor: grafos de Blueprint y C++ (traducido a un script que valida la plataforma). Ambas se analizan y se suben como cualquier declaración, y se siguen ejecutando en la plataforma.
Tanto una sobrescritura como un hook son funciones en la nube: se ejecutan en la plataforma, no en el motor. Escríbelas en C#, TypeScript o Python; Unity se suscribe a los eventos que generan.
El panel dibuja justamente la forma declarada: en cada punto ves sus handlers, su tipo y el orden final en que se ejecutan. El tipo decide qué pasa cuando falla un handler:
| Tipo | Si falla el propio handler | Al rechazar |
|---|---|---|
| un filtro (gate) | el paso se rechaza: una verificación de región que no se pudo ejecutar no cuenta como aprobada | un código del catálogo de la plataforma más un motivo legible para una persona. Quien llama decide según el código; el texto del motivo puede cambiar y traducirse |
| un observador | el paso sigue hecho: si el kit de inicio no llegó, se pierde un cofre, no el inicio de sesión | no puede rechazar |
Un hook nunca decide quién inició sesión
Un filtro responde sí o no sobre una identidad que la plataforma ya estableció. No nombra al jugador, no entrega una identidad ni reemplaza la confirmación del proveedor.
El modelo
Un jugador es portador de identidades, no una fila de tu esquema, y su identificador es estable y nunca se reutiliza, ni siquiera en una fusión: el id de un jugador fusionado se sigue resolviendo en lugar de convertirse en una referencia colgante. Un vínculo es una terna: proveedor · sujeto externo · jugador.
| Siempre | Qué es |
|---|---|
proveedor + sujeto | es único, y esa unicidad genera un conflicto, no una prohibición: ante “esta cuenta ya está en uso”, la respuesta es elegir una fusión |
como máximo un vínculo por proveedor para cada jugador | una segunda cuenta del mismo proveedor es un conflicto |
un sujeto externo | nunca es el identificador de un jugador: pertenece al proveedor, y usarlo como propio ataría nuestros ids a los suyos |
tipo de identidad y estado de acceso | son ejes distintos: anonymous frente a registered es uno; active / suspended / banned es otro, así que se puede expresar tanto “anónimo baneado” como “registrado suspendido” |
una sesión y una credencial | son cosas distintas: la sesión es el registro; la credencial es lo que presentas. Revocar una sesión invalida todas sus credenciales y cierra sus suscripciones abiertas |
muchas sesiones simultáneas | cada una se revoca por separado |
las claims de una credencial | son contexto declarado — región, idioma — y solo contexto. Una claim nunca otorga autoridad |
una huella del dispositivo | no es una identidad: nunca es motivo para admitir, solo para rechazar, y se guarda y se compara de forma irreversible |
Las tres máquinas de estados.
| Máquina | Estados |
|---|---|
| el tipo de identidad | anonymous → registered, y la transición va en un solo sentido |
| el estado de acceso | active ⇄ suspended, y active → banned → active al levantar un baneo |
| el jugador | alive → merged, donde merged es terminal: un jugador fusionado no vuelve a iniciar sesión |
Lo que declaras tú.
| Declaración | Qué es |
|---|---|
política de inicio de sesión | si se permite el inicio de sesión anónimo y el resto de las reglas para iniciar sesión. Se declara como un atributo en el punto de montaje del módulo: no es un archivo de configuración junto al código ni algo que se construye en runtime |
rol predeterminado | el conjunto de roles con el que llega un jugador nuevo en su primer inicio de sesión. El valor predeterminado no tiene valor predeterminado: si no declaras nada, los jugadores nuevos llegan sin roles, y eso es una declaración legítima, no un olvido |
política de sesiones | qué pasa cuando se alcanza el tope de sesiones simultáneas: expulsar la más antigua con un evento o rechazar la nueva. Sin valor predeterminado |
política de eliminación | cómo llega la eliminación de un jugador a los datos que hacen referencia a él |
Dónde se configura un proveedor. En el plano de operador, no en el código: la credencial de una tienda digital no tiene por qué estar en un repositorio. Lo declarado llega a la consola de administración solo para lectura.
Qué implica otorgar un rol. Los roles no son solo asunto del operador: la interfaz incluye otorgar y revocar para un jugador, así que un juego puede ascender a un oficial de gremio o darle sus poderes al organizador de un torneo desde su propio código.
| Siempre | Qué es |
|---|---|
no es autopromoción | para otorgar un rol hace falta el permiso atómico declarado para eso, y un actor sin ese permiso recibe un simple forbidden, no una operación que en silencio no hace nada |
otorgar es idempotente | otorgar un rol que el jugador ya tiene es un éxito, no un conflicto: el estado es el conjunto de roles, no el historial de llamadas, así que, a diferencia del inicio de sesión, esta operación no necesita clave de idempotencia |
un cambio de rol no es instantáneo | entra en vigor sin volver a emitir la credencial y se vuelve visible, a más tardar, al cumplirse el límite de desactualización declarado para la caché de permisos. Si tu código otorga un rol y lo verifica de inmediato en un cliente conectado, tiene que contemplar esa ventana |
De qué están hechos los roles y qué desbloquean se explica en Access & Roles.
Errores
- Sin credencial, o con una vencida, la respuesta es not authenticated y una renovación lo resuelve. Una credencial revocada recibe la misma respuesta, pero renovarla no sirve: solo un nuevo inicio de sesión.
- Volver a presentar una credencial ya rotada es un conflicto: gracias a eso la rotación se puede detectar en lugar de tolerarse en silencio.
- Un jugador baneado o suspendido, al igual que una huella baneada, recibe forbidden, y no tiene sentido repetir.
- Que el par proveedor+sujeto ya esté en uso es un conflicto, y se puede repetir después de elegir una fusión; una segunda cuenta del mismo proveedor es un conflicto que ningún reintento va a cambiar.
- Desvincular el último método de inicio de sesión es un rechazo de validación: dejaría una cuenta a la que nadie puede entrar.
- Fusionar un jugador que ya fue fusionado es un conflicto:
mergedes terminal. - Si el proveedor no está disponible, la respuesta es unavailable y vale la pena reintentar con backoff; si el proveedor rechaza la credencial, la respuesta es not authenticated y vale un reintento, no un bucle.
- Si se supera la frecuencia de intentos de inicio de sesión, la respuesta cae en la categoría de límite de frecuencia e indica un plazo.
Límites
Los números llegarán con el capítulo sobre los límites de la plataforma; lo que pasa al alcanzar cada tope ya está definido.
- Sesiones simultáneas por jugador: según la política declarada, se expulsa la más antigua con un evento o se rechaza la nueva. No hay valor predeterminado.
- Intentos de inicio de sesión por período e intentos de vincular un par en uso: un límite de frecuencia con plazo, y el contador de intentos queda en el historial.
- Vínculos por jugador: vincular otro proveedor se rechaza como conflicto.
- Vida útil de una credencial: not authenticated, y se puede repetir tras una renovación. Vida útil de una credencial de renovación: solo un nuevo inicio de sesión.
- Retención de un jugador anónimo que no inicia sesión: se elimina según la política declarada, con un evento. La política se declara de forma explícita; no hay valor predeterminado.
- Entradas en la lista de huellas baneadas: se rechaza la nueva entrada, y las antiguas nunca se expulsan en silencio.