Forcer la réauthentification dans OIDC

Le mécanisme prompt=login peut être contourné en supprimant simplement le paramètre lors qu’il est passé à l’agent utilisateur (navigateur) et n’est utile que pour fournir une indication d’expérience utilisateur (UX) au fournisseur OpenID dans les cas où la partie de confiance souhaiterait afficher un lien tel que :

« Salut Josh. Vous n’êtes pas Josh? Cliquez ici. »

Cependant, vous ne devriez pas vous fier à cela pour valider qu’une nouvelle authentification a eu lieu. Pour atténuer cela, le client doit valider que la réauthentification a eu lieu en utilisant la demande auth_time si la réauthentification est la raison pour laquelle max_age a été demandé. Cette demande sera incluse automatiquement dans le jeton d’ID lorsque les paramètres prompt-login ou max_age=0 sont fournis dans la demande d’authentification.

Vous devez passer le paramètre max_age au point de terminaison /authorize de l’Authorization API. Si vous utilisez Auth0.js ou Lock, vous pouvez définir le paramètre dans les options appropriées de la bibliothèque.

La façon dont vous implémentez la réauthentification dépend de votre cas d’utilisation spécifique. Faites une distinction entre la réauthentification simple pour des opérations sensibles et la réauthentification multifacteur (c’est-à-dire l’authentification multifacteur (MFA)) pour des opérations sensibles. Les deux sont des mesures de sécurité valides. La première nécessite que l’utilisateur final saisisse à nouveau son mot de passe, tandis que la seconde exige également qu’il utilise un moyen d’authentification multifacteur préconfiguré.

Limitations des paramètres prompt=login

La spécification OIDC définit le paramètre prompt=login qui peut être utilisé pour déclencher une interface utilisateur de réauthentification (généralement une invite de connexion) :

Cependant, il y a un problème avec l’utilisation de ce paramètre pour garantir la réauthentification : la partie de confiance n’a aucun moyen de valider le fait qu’une action de réauthentification a eu lieu. Examinons le trafic pour en connaître les raisons. Le flux pour une demande d’authentification de la part de la partie de confiance est le suivant :

https://mydomain.auth0.com/authorize?
client_id=abcd1234
&redirect_uri= https://mydomain.com/callback

&scope=openid profile
&response_type=id_token
&prompt=login

Was this helpful?

/

Après une authentification réussie par le serveur d’autorisation, la partie de confiance recevra un jeton d’ID :

{
  "nickname": "user",
  "name": "user@mydomain.auth0.com",
  "updated_at": "2019-04-01T14:43:03.445Z",
  "iss": "https://jcain0.auth0.com/",
  "sub": "auth0|l33t",
  "aud": "abcd1234",
  "iat": 1554129793,
  "exp": 1554165793
}

Was this helpful?

/

Le document d’identité de confiance retourné par le serveur d’autorisation ne contient aucune demande validant la date de la dernière connexion. Cela devient un problème lorsque la demande d’autorisation initiale se présente sous la forme d’une redirection 302 via le navigateur de l’utilisateur final. Si un acteur malveillant souhaite sauter l’étape de ré-authentification demandée par la partie de confiance, il lui suffit tout simplement de supprimer le paramètre prompt=login et la partie de confiance ne remarquera pas la différence dans les champs contenus dans le jeton d’ID.

Voici un diagramme de flux implicite simplifié utilisant le paramètre prompt=login :

Force Re-Authentication OIDC Implicit Flow

Remarque : tout ce que l’utilisateur final doit faire est de supprimer le paramètre prompt=login. Ainsi, l’étape de réauthentification pourra être contournée :

Simplified Implicit Flow Remove prompt=login

Le(s) jeton(s) renvoyé(s) par le premier flux ci-dessus seront identiques au(x) jeton(s) renvoyés par le second flux. La partie de confiance ne dispose pas de moyen défini par la spécification pour vérifier qu’une réauthentification a eu lieu, et ne peut donc pas être confiante quant au fait qu’un prompt=login a réellement entraîné une ré-authentification.

paramètre de requête d’authentification max_age

Contrairement à prompt=login, le paramètre de requête d’authentification max_age fournit un mécanisme permettant aux parties de confiance de confirmer positivement que la réauthentification a eu lieu dans un intervalle de temps donné. La spécification OIDC stipule :

La dernière phrase de la définition est la partie la plus importante. Lorsque max_age est demandé par la partie de confiance, une demande auth_time doit être présente dans cette requête. Cela signifie que max_age peut être utilisé de deux manières :

  • Pour appliquer une fraîcheur minimale de session : Si une application exige que les utilisateurs se réauthentifient une fois par jour, cela peut être appliqué dans le cadre d’une session SSO beaucoup plus longue en fournissant max_age avec une valeur. Celles-ci sont définies en secondes.

  • Pour forcer une réauthentification immédiate: Si une application exige qu’un utilisateur se réauthentifie avant tout accès, fournissez une valeur de 0 pour le paramètre max_age et le serveur d’autorisation forcera une nouvelle connexion.

Cette exigence est décrite comme suit :

OIDC re-authentication max_age flow

Remarque : la partie de confiance reçoit un jeton contenant la quantité d’informations appropriée pour valider si la réauthentification a eu lieu ou non. La partie de confiance peut désormais consulter la demande auth_time dans le jeton d’ID pour déterminer si le paramètre max_age qu’elle a demandé a été respecté. De cette manière, le paramètre max_age=0 est résistant au même type de manipulation par le client qui pourrait compromettre le paramètre prompt=login .

Utilisez les demandes auth_time.

Nous avons établi que la spécification OIDC fournit le paramètre max_age comme un moyen de confirmer positivement qu’une réauthentification a eu lieu, tandis que prompt=login ne le fait pas. Cela ne présente pas d’options très sécurisées si vous souhaitez forcer une réauthentification :

  • prompt=login: En incluant uniquement le paramètre prompt, vous ne validez pas que le serveur d’autorisation a réellement ré-authentifié.

  • prompt=login & max_age=999999: Vous pouvez inclure un max_age arbitraire de sorte qu’une demande auth_time soit présente. Vous pouvez valider qu’une réauthentification a eu lieu, mais les paramètres se compliquent.

  • max_age=0: Forcer efficacement une invite de connexion en utilisant uniquement le paramètre max_age . Remarque : une mise à jour récente de la spécification a clarifié ce paramètre, indiquant qu’il présente la même efficacité que prompt=login. Il n’est pas souhaitable de combiner ces deux paramètres, car il s’agit d’un mélange de ce qui devrait être un paramètre UX avec un paramètre d’entretien de session.

Au lieu de cela, Auth0 a choisi d’envoyer la demande auth_time dans le jeton d’ID lorsqu’il répond à un paramètre de requêteprompt=login . Cela signifie que vous avez la latitude d’utiliser prompt=login ET de valider qu’une réauthentification a eu lieu.

Exemple de validation auth_time

L’exemple suivant utilise le module passport-auth0-openidconnect pour démontrer comment valider la réauthentification. La première (et la plus simple) façon consiste à ajouter l’option max_age=0 à Auth0OidcStrategy:

var strategy = new Auth0OidcStrategy(
  {
    domain: process.env.AUTH0_DOMAIN,
    clientID: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    callbackURL: process.env.AUTH0_CALLBACK_URL || 'http://localhost:5000/callback',
    max_age: 0
  },
  function(req, issuer, audience, profile, accessToken, refreshToken, params, cb) {
    // No extra validation required!
    return cb(null, profile);
  });

Was this helpful?

/

Remarquez qu’aucune étape de validation supplémentaire n’est requise, car la stratégie gère déjà la validation du paramètre max_age :

// https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation - check 8.
if (meta.params.max_age && (!jwtClaims.auth_time || ((meta.timestamp - meta.params.max_age) > jwtClaims.auth_time))) {
  return self.error(new Error('auth_time in id_token not included or too old'));
}

Was this helpful?

/

Vous pouvez également utiliser prompt=login dans le même contexte, mais comme la norme n’exige pas qu’un auth_time accompagne la réponse du jeton d’ID, vous devez gérer la validation manuellement. Donc, le constructeur de la stratégie serait :

var strategy = new Auth0OidcStrategy(
  {
    domain: process.env.AUTH0_DOMAIN,
    clientID: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    callbackURL: process.env.AUTH0_CALLBACK_URL || 'http://localhost:5000/callback',
    prompt: 'login'
  },
  function(req, issuer, audience, profile, accessToken, refreshToken, params, cb) {
    const tenSecondsAgo = (Date.now() / 1000) - 10;
    if (isNaN(profile.auth_time) || profile.auth_time < tenSecondsAgo) {
      return cb('prompt=login requested, but auth_time is greater than 10 seconds old', null);
    }

    return cb(null, profile);
  });

Was this helpful?

/

Contrairement à max_age=0, le client doit effectuer manuellement la validation du paramètre auth_time Pour en savoir plus, lisez Utiliser les demandes auth_time.

Problèmes connus

Auth0 ne peut que garantir qu’un échange a eu lieu avec le fournisseur d’identité en amont. Cela peut se faire par le biais de l’utilisateur se connectant réellement à un fournisseur d’identité tiers, ou peut-être que l’utilisateur avait déjà une session et n’a pas eu besoin de se connecter à nouveau. Quoi qu’il en soit, l’échange d’Auth0 avec le fournisseur d’identité en amont entraînera une mise à jour de auth_time.

Forcer la réauthentification au sein du fournisseur d’identité en amont n’est pas une chose prise en charge par Auth0, car ne sont pas tous les fournisseurs qui le prennent en charge.

Le diagramme ci-dessous présente un exemple de flux pour un utilisateur qui choisit de se réauthentifier au moyen d’une connexion fédérée :

Federated connections do not force re-authentication diagram

Cette méthode suppose que vous utilisez des connexions à une base de données. Les fournisseurs d’identité externes peuvent ou non prendre en charge la réauthentification forcée. Utiliser prompt=login ou prompt=consent  est généralement un moyen d’indiquer à un fournisseur d’identité externe (social) de réauthentifier un utilisateur, mais Auth0 ne peut pas le garantir.

En savoir plus