Skip to content
← Tous les articles
· Delta1 Labs LicencesDroitsGuide

Une licence, plusieurs horloges : l'expiration par droit avec Keyright

Une licence n'a généralement qu'une seule date d'expiration, mais les contrats réels en ont rarement une seule : un forfait de base se renouvelle chaque année, tandis qu'un module complémentaire d'essai dure 30 jours et qu'une fonctionnalité promotionnelle prend fin avec le trimestre. Imposer une seule date à toute la licence vous oblige soit à accorder trop, soit à réémettre sans cesse. Voici comment donner à chaque droit sa propre expiration au sein d'une seule licence Keyright signée : la forme de la charge utile, le SDK qui lit hors ligne une horloge par fonctionnalité, la façon de l'appliquer à la frontière de la capacité, et les règles de grâce qui empêchent un module expiré de mettre à terre toute l'application.

La plupart des piles de licences donnent à une licence exactement une horloge : un expiresAt pour l’ensemble de la clé. Cela modélise le contrat le plus simple — un abonnement annuel qui se renouvelle ou expire — et rien d’autre. Les accords commerciaux réels sont plus désordonnés. Un client est sur un forfait de base annuel, essaie un module complémentaire premium pendant 30 jours et bénéficie en plus d’une fonctionnalité promotionnelle que vous lui avez offerte jusqu’à la fin du trimestre. Trois capacités, trois dates de fin différentes, un seul client.

Avec une licence à horloge unique, vous avez deux mauvaises options. Soit vous émettez plusieurs licences — une par date de fin — et votre application doit désormais jongler avec un sac de clés et les réconcilier, soit vous choisissez la date la plus tardive pour toute la licence et vous accordez silencieusement l’essai et la promo au-delà de leur terme. Aucune des deux n’est honnête. La solution consiste à cesser de considérer l’expiration comme une propriété de la licence et à la traiter comme une propriété de chaque droit.

Un droit peut porter sa propre horloge

Si vous avez lu Droits et indicateurs de fonctionnalités pilotés par la licence, vous savez qu’un droit est une capacité nommée — un indicateur booléen comme export, ou une limite numérique comme max-projects — intégrée dans la licence signée à partir d’un modèle de niveau. L’expiration par droit ajoute un champ : chaque droit peut préciser quand il cesse de s’appliquer.

La charge utile de la licence reste un seul objet signé ; le bloc des droits s’enrichit simplement :

{
  "sub": "acct_8f21c",
  "tier": "pro",
  "iat": "2026-10-12T00:00:00Z",
  "exp": "2027-10-12T00:00:00Z",
  "entitlements": {
    "export":        { "value": true, "exp": "2027-10-12T00:00:00Z" },
    "sso":           { "value": true },
    "premium-trial": { "value": true, "exp": "2026-11-11T00:00:00Z" },
    "promo-reports": { "value": true, "exp": "2026-12-31T23:59:59Z" },
    "max-projects":  { "value": 25,   "exp": "2027-10-12T00:00:00Z" }
  }
}

Trois horloges sont désormais visibles. L’exp de premier niveau est la durée de vie propre de la licence — la limite extérieure stricte. premium-trial meurt le 11 novembre, promo-reports le soir du Nouvel An, et sso n’a aucun exp, ce qui signifie qu’il vit exactement aussi longtemps que la licence. Chaque droit est évalué par rapport à sa propre date, bornée par celle de la licence.

Durée de vie de la licence (un seul objet signé)Oct 2026Oct 2027sso — pas d'exp, couvre toute la licencepremium-trialse termine dans 30 jours → IsEnabled falsepromo-reportsse termine le 31 déc. → IsEnabled false, licence toujours validemaintenant+30 jfin du trimestre

Lire une horloge par fonctionnalité dans votre application

La surface du SDK change à peine, et c’est précisément le but : IsEnabled et GetLimit renvoient déjà la réponse effective, et la question « l’horloge propre à ce droit est-elle arrivée à échéance ? » est simplement intégrée à cette réponse. Un droit expiré est signalé comme désactivé :

var client = KeyrightClient.Initialize(options);
await client.ActivateAsync(licenseKey);   // once; the lease is cached and verified

// premium-trial is true in the payload, but if its exp has passed,
// IsEnabled returns false — no special-casing at the call site.
if (client.IsEnabled("premium-trial"))
    ShowPremiumPanel();

// A numeric limit whose clock has run out falls back to your conservative value.
int maxProjects = client.GetLimit("max-projects", fallback: 1);

Parce que toute la décision est locale — comparer l’exp du droit à l’heure actuelle, dans une charge utile dont vous avez déjà vérifié la signature avec la clé publique incorporée — il n’y a aucun aller-retour réseau et rien de nouveau qui puisse échouer. L’évaluation effectuée par IsEnabled ressemble en gros à ceci :

internal bool IsEnabled(string key)
{
    if (!_entitlements.TryGetValue(key, out var e)) return false;   // unknown → closed
    if (e.Value is not true) return false;                          // flag is off
    if (e.Expiry is DateTimeOffset exp && _clock.UtcNow >= exp)
        return false;                                               // this feature's clock ran out
    return true;
}

Tout ce sur quoi votre code existant s’appuyait déjà reste vrai : les indicateurs inconnus échouent en mode fermé, la signature est vérifiée hors ligne, et falsifier un horodatage invalide toute la licence. Le seul ajout est l’unique comparaison _clock.UtcNow >= exp, limitée à un seul droit.

Lorsque vous avez besoin de la date elle-même — pour afficher « Votre essai premium se termine dans 6 jours » — demandez-la directement plutôt que de fouiller dans la charge utile :

DateTimeOffset? ends = client.ExpiresAt("premium-trial");
if (ends is { } d && d - DateTimeOffset.UtcNow < TimeSpan.FromDays(7))
    ShowTrialEndingBanner(d);

ExpiresAt renvoie null pour un droit sans horloge (il vit aussi longtemps que la licence) et la date effective sinon — jamais une date postérieure à l’exp propre de la licence, car la licence reste la borne extérieure.

Appliquer la règle à la frontière de la capacité

La règle valable pour les droits en général s’applique telle quelle : masquer un bouton relève de l’UX, pas de l’application de la licence. Placez la vérification là où la capacité vit réellement, et laissez le cas de l’essai expiré découler du même appel IsEnabled :

public Report BuildPromoReport(ReportOptions options)
{
    if (!_license.IsEnabled("promo-reports"))
        throw new FeatureNotLicensedException("promo-reports");

    return _renderer.Render(options);
}

Le 30 décembre, ce code s’exécute. Le 1er janvier, le même code lève une exception — non pas parce que vous avez livré quoi que ce soit, ni parce que la licence est devenue invalide, mais parce que l’horloge de ce droit-là est arrivée à échéance. Le client conserve export, sso et tout le reste de la licence jusqu’à l’exp propre de la licence.

Grâce, décalage et horloge fiable

L’expiration par fonctionnalité hérite des deux mêmes risques que toute vérification basée sur le temps, et vous les traitez de la même manière.

Décalage et recul de l’horloge. Une vérification hors ligne n’est honnête que dans la mesure où l’horloge de l’appareil l’est. Un utilisateur qui recule son horloge d’un mois ressuscite un essai mort — à moins que vous ne le détectiez. La détection de manipulation de l’horloge de Keyright (traitée dans Manipulation de l’horloge de licence et recul du temps) persiste un repère monotone de la dernière heure vue par l’application, et refuse d’accepter une horloge murale qui aurait reculé au-delà de ce repère. L’expiration par droit se place directement au-dessus : le _clock.UtcNow de l’évaluation ci-dessus est l’heure validée, pas le DateTimeOffset.UtcNow brut.

Grâce, décidée par capacité. Lorsque la licence entre dans sa fenêtre de grâce, vous laissez généralement l’application fonctionner en lecture seule pendant quelques jours (voir Indisponibilité du serveur de licences et fenêtres de grâce). Un seul droit qui expire est plus doux : par défaut, c’est une coupure nette à son exp, parce que le reste de l’application n’est pas affecté et que le client ne perd que le module complémentaire. Si vous voulez un atterrissage en douceur pour une fonctionnalité précise — disons quelques jours pendant lesquels un essai se dégrade en lecture seule avant de disparaître — encodez cela comme une politique là où vous lisez l’indicateur, pas comme une seconde date d’expiration :

public bool CanEditWithPremium()
{
    if (_license.IsEnabled("premium-trial")) return true;

    // Optional soft landing: 3 read-only days after the trial clock lapses.
    var ended = _license.ExpiredAt("premium-trial");   // when it lapsed, or null
    return ended is { } t && DateTimeOffset.UtcNow - t < TimeSpan.FromDays(3)
        ? false   // view allowed elsewhere; editing is off
        : false;
}

Garder la grâce dans votre code plutôt que dans la charge utile signifie que la licence dit une seule chose honnête — cette fonctionnalité se termine à cette date — et que votre produit décide avec quelle douceur la lâcher.

Pourquoi c’est mieux que de jongler avec des clés

L’expiration par droit fait disparaître toute une catégorie de douleurs opérationnelles :

  • Une clé par client, toujours. Un module d’essai, une promo et un forfait de base avec trois dates de fin différentes sont toujours livrés sous forme d’une seule licence. Votre flux d’activation, votre stockage et votre équipe support n’ont affaire qu’à une seule clé.
  • Les promos et les essais expirent d’eux-mêmes. Vous émettez la licence une fois, avec la date de fin de la promo sur ce seul droit. Personne n’a à se souvenir de réémettre ou de révoquer quand le trimestre change — l’horloge est déjà dans la charge utile signée.
  • Vente additionnelle sans précipice. Quand l’essai premium expire, l’application retombe sur l’expérience de base que le client a payée, pas sur un écran de verrouillage. C’est le moment d’afficher une invite de mise à niveau, et ExpiresAt vous donne la date exacte autour de laquelle la construire.
  • Cela reste hors ligne et toute falsification est détectable. Chaque horloge voyage dans le même modèle signé, vérifié localement. Pas de nouveau point de terminaison, pas de nouvel aller-retour, et toute modification d’une date quelconque casse la signature.

Une licence n’a jamais vraiment été un seul contrat avec une seule date de fin — c’était simplement la chose la plus facile à modéliser. Donnez à chaque droit sa propre horloge et la licence correspond enfin à l’accord que vous avez réellement vendu. Consultez le guide des droits Keyright pour le modèle de capacités sur lequel cela s’appuie, et la manipulation de l’horloge de licence pour l’horloge validée dont cela dépend.

Essayez Nebula.NET

Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.