Qué debe hacer tu app cuando el servidor de licencias está caído
Una comprobación de licencia que falla en seco en cuanto el servidor es inalcanzable convierte tu caída — o el Wi-Fi inestable del cliente — en la caída de ellos. Aquí tienes cómo diseñar ventanas de gracia, elegir fail-open vs fail-closed por nivel, y evitar que un corte de red pasajero deje fuera a clientes que pagan.
Toda comprobación de licencia en línea tiene un modo de fallo que la mayoría de los equipos no diseña: el servidor sencillamente no es alcanzable. Quizá tu servicio de licencias tiene un mal día; quizá el proxy corporativo del cliente se comió la petición; quizá su portátil va en un tren. Si tu comprobación trata «no pude alcanzar el servidor» igual que «el servidor dijo que esta licencia es inválida», entonces tu caída se convierte en la caída de tu cliente — y un parpadeo de red de treinta segundos bloquea a un usuario que paga fuera de un software que posee. El arreglo es diseñar explícitamente para la inalcanzabilidad, con ventanas de gracia y una política deliberada de fail-open/fail-closed.
«No» y «no lo sé» son respuestas distintas
La distinción más importante en el licenciamiento resiliente es entre dos resultados muy diferentes de un check-in:
- Un negativo autoritativo — alcanzaste el servidor y dijo que esta licencia está caducada, revocada o por encima de su conteo de asientos. Eso es un «no» real, y debe surtir efecto.
- Un resultado inconcluso — no pudiste alcanzar el servidor en absoluto: fallo de DNS, timeout, 503, error de TLS. Eso no es un «no». Es «no lo sé ahora mismo».
Una comprobación ingenua colapsa ambos en «no válida → detente». Ese es el bug. La causa abrumadoramente probable de un resultado inconcluso es un problema de red pasajero, no un cliente que pirateó tu software a mitad de sesión. Tratar el silencio como una denegación castiga a la mayoría honesta para incomodar a un atacante que de todas formas tiene opciones mucho más fáciles (puede simplemente correr sin conexión). El licenciamiento resiliente mantiene los dos casos separados y responde a cada uno apropiadamente.
El lease cacheado es a lo que recurres
La resiliencia descansa sobre un lease firmado que la app ya tiene de su último check-in con éxito — la misma primitiva de validación sin conexión descrita en validación de licencias sin conexión. El lease es una declaración firmada por el servidor, «esta licencia es válida hasta el momento T», que la app puede verificar localmente con una clave pública embebida, sin red. Su ventana de validez es la columna vertebral de la gracia: entre check-ins la app confía en el lease, y cuando un check-in no puede completarse, sigue confiando en el lease hasta que esa ventana — más un periodo de gracia explícito — se agote.
El parámetro de diseño clave es hacer la ventana del lease cómodamente más larga que el intervalo de check-in. Si haces check-in a diario pero emites un lease de 7 días, un cliente puede estar sin conexión (o tu servidor caído) la mayor parte de una semana antes de que algo se degrade. Un check-in, entonces, no es una barrera que debe pasar en cada arranque; es un refresco oportunista que extiende la pista:
async Task<LicenseState> EvaluateAsync(CachedLease lease, DateTime now)
{
try
{
var fresh = await _server.CheckInAsync(lease.Key); // reach the server
if (fresh.Status == Status.Revoked || fresh.Status == Status.Expired)
return LicenseState.Denied(fresh.Status); // authoritative NO — act on it
_store.Save(fresh.Lease); // refresh: extend the runway
return LicenseState.Active(fresh.Lease);
}
catch (Exception) // timeout, DNS, 5xx, TLS — INCONCLUSIVE, not a denial
{
return EvaluateOffline(lease, now); // fall back to the cached lease
}
}
LicenseState EvaluateOffline(CachedLease lease, DateTime now)
{
if (now <= lease.ValidThroughUtc)
return LicenseState.Active(lease); // still inside the lease window
if (now <= lease.ValidThroughUtc + GracePeriod)
return LicenseState.Grace(lease, until: lease.ValidThroughUtc + GracePeriod);
return LicenseState.NeedsReconnect(); // runway exhausted — degrade, don't crash
}
Fíjate en la asimetría: una respuesta Revoked/Expired termina la sesión, pero cualquier excepción recurre al lease. El error de red nunca deniega; solo el servidor deniega.
Falla en abierto, luego degrada — rara vez bloquea en duro
Con los dos casos separados, la política se escribe sola para la mayoría de los productos:
- Dentro de la ventana del lease: corre normal. Nada que decidir.
- Pasado el lease, dentro de la gracia: corre normal, pero empieza a avisar — un discreto banner de «no hemos podido verificar tu licencia; seguiremos intentándolo». Reintenta en segundo plano con backoff exponencial (no machaques un servidor que ya puede estar sufriendo).
- Pasada la gracia: degrada, no te cierres. Baja a solo lectura, deshabilita funciones solo de pago, o bloquea trabajo nuevo dejando que el usuario guarde y exporte lo que tiene. Un mensaje claro de «reconéctate para continuar» convierte un callejón sin salida en un problema resoluble.
El principio rector es que tu licenciamiento debe fallar seguro para el cliente, no fallar agresivo. El coste de bloquear por error a un cliente que paga durante tu propia caída — tickets de soporte, reembolsos, abandono, reputación — empequeñece el coste de que un pirata decidido consiga unos días extra de gracia que de todas formas podría haber conseguido quedándose sin conexión. Los productos acotados por derechos se degradan especialmente bien: puedes deshabilitar las banderas premium dejando la app central usable.
Cuándo el fail-closed es la decisión correcta
Fail-open-dentro-de-la-gracia es el valor por defecto correcto, no una ley universal. Dos situaciones justifican un comportamiento más estricto:
- Una revocación autoritativa. Si el servidor es alcanzable y devuelve «revocada» — un reembolso, un contracargo, una clave comprometida — eso es un no real y debe surtir efecto pronto, por lo que la revocación viaja por la ruta del check-in y no está sujeta a la ventana de gracia. La gracia cubre el silencio, nunca una denegación explícita.
- Niveles de alto valor o sujetos a cumplimiento. Para un modelo flotante/concurrente donde un asiento debe liberarse, o un despliegue regulado donde el uso no verificado es en sí un problema, un lease más corto y una gracia más corta son apropiados — el modelo de negocio, no la red, fija la tolerancia. Hazlo una política por nivel: una app de consumo puede permitirse una semana generosa de gracia; una herramienta de servidor con asientos concurrentes podría permitir solo horas.
El punto es que el fail-closed debe ser una elección deliberada para niveles específicos, impulsada por el valor en juego — no el valor por defecto accidental que obtienes al tratar un timeout como una denegación.
Ajustando los mandos
Tres parámetros dan forma a toda la experiencia, y deben ser explícitos, no emergentes:
- Ventana de validez del lease — cuánto compra un único check-in con éxito. Más larga = más resiliente a caídas, más lenta en reflejar una revocación. Días para la mayoría del software de escritorio/B2B.
- Periodo de gracia — tiempo extra pasado el lease antes de degradar. Un margen de seguridad específicamente para tus caídas; incluso un día reduce de forma significativa los bloqueos falsos.
- Cadencia de check-in + backoff — cada cuánto refrescas cuando está sano, y cómo reintentas cuando falla. Refresca bastante antes de que el lease caduque (para que unos pocos check-ins perdidos sean inofensivos), y haz backoff al fallar para no estampar un servidor que sufre.
Ajusta estos por nivel y obtienes resiliencia por construcción: las caídas breves son invisibles, las más largas se degradan con suavidad y mensajes claros, y solo un «no» real y autoritativo detiene en seco a un cliente.
La conclusión
Una comprobación de licencia no es solo una barrera de sí/no — tiene un tercer estado, «no pude preguntar», y cómo manejas ese estado es lo que separa el licenciamiento que protege tus ingresos del licenciamiento que de vez en cuando destruye tu buena voluntad. Cachea un lease firmado, dale una ventana más larga que tu intervalo de check-in, añade un periodo de gracia encima, y haz que el estado terminal sea una degradación elegante con un camino de vuelta. Trata el «no» del servidor como autoritativo y el silencio de la red como «todavía no». Haz eso, y la próxima vez que tu servicio de licencias tenga una mala hora, tus clientes ni se enterarán.
Prueba Nebula.NET
Endurece tu código .NET en minutos — empieza con la edición gratuita.