Saltar al contenido principal
Servicios alrededor del juego

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: Link agrega 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 banned que 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 auth no 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é

ActorQué hace en esta página
playerinicia sesión, vincula o desvincula identidades, renueva sus credenciales, cierra sesión
moderatorrevoca sesiones; banea, suspende o rehabilita jugadores
backend-servicefiltra el inicio de sesión por región; crea las primeras filas de un jugador nuevo; lee y revoca sesiones

En resumen

Una llamada a 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 identities
Próximamente — Go

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.

// 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.
Unity C# is the same C# API here — funciona tal cual en Unity con los tipos generados
// 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

Cada punto se personaliza en el mismo lugar donde se declara. Todas las formas que puede tomar un handler están reunidas en Extensibility:

Un filtro antes del inicio de sesión rechaza una región; un observador después del inicio de sesión que creó al jugador le otorga un kit de inicio
[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)
Próximamente — Go

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.

Se ejecuta fuera del motor

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.

Se ejecuta fuera del motor

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:

TipoSi falla el propio handlerAl rechazar
un filtro (gate)el paso se rechaza: una verificación de región que no se pudo ejecutar no cuenta como aprobadaun 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 observadorel paso sigue hecho: si el kit de inicio no llegó, se pierde un cofre, no el inicio de sesiónno 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

device · subject (the guest)Steam · subject (via Link)another player, same persona player — one stable id, never reused linklink: anonymous → registered, one waymerge: terminal; its id keeps resolving
Un vínculo es proveedor · sujeto · jugador: vincular convierte a un invitado en un solo sentido, y tras una fusión el id fusionado se sigue resolviendo

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.

SiempreQué es
proveedor + sujetoes ú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 jugadoruna segunda cuenta del mismo proveedor es un conflicto
un sujeto externonunca 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 accesoson 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 credencialson 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áneascada una se revoca por separado
las claims de una credencialson contexto declarado — región, idioma — y solo contexto. Una claim nunca otorga autoridad
una huella del dispositivono 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áquinaEstados
el tipo de identidadanonymous → registered, y la transición va en un solo sentido
el estado de accesoactive ⇄ suspended, y active → banned → active al levantar un baneo
el jugadoralive → merged, donde merged es terminal: un jugador fusionado no vuelve a iniciar sesión

Lo que declaras tú.

DeclaraciónQué es
política de inicio de sesiónsi 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 predeterminadoel 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 sesionesqué 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óncó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.

SiempreQué es
no es autopromociónpara 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 idempotenteotorgar 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áneoentra 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: merged es 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.