Loupe

Validation du issuer avec Azure AD en mode multitenant en ASP.NET Core

Si vous utilisez l'authentification Azure AD et que vous voulez permettre à des utilisateurs de n'importe quel tenant de se connecter à votre application ASP.NET Core, il faut configurer votre app Azure AD comme multi-tenant, et utiliser un id de tenant "wildcard", comme organizations ou common, dans l'URL de l'authorité :

openIdConnectOptions.Authority =
    "https://login.microsoftonline.com/organizations/v2.0";

Le problème si vous faites cela, est qu'avec la configuration par défaut, la validation du token échouera, car le issuer dans le token ne correspondra pas à celui spécifié dans les métadonnées OpenID. C'est parce que le issuer indiqué dans les métadonnées contient un placeholder pour l'id du tenant :

https://login.microsoftonline.com/{tenantid}/v2.0

Mais le claim iss dans le token contient l'URL pour le tenant effectif, par exemple :

https://login.microsoftonline.com/64c5f641-7e94-4d21-ae5c-9747994e4211/v2.0

Une solution de contournement qui est souvent suggérée est de désactiver la validation du issuer dans les paramètres de validation du token :

openIdConnectOptions.TokenValidationParameters.ValidateIssuer = false;

Cependant, si vous faites cela, le issuer ne sera pas validé du tout. Bien sûr, ce n'est peut-être pas un problème majeur, dans la mesure où la signature du token prouvera l'identité du issuer, mais dans le principe ça m'a toujours gêné…

Heureusement, il est possible de contrôler comment le issuer est validé, en spécifiant la propriété TokenValidator :

options.TokenValidationParameters.IssuerValidator = ValidateIssuerWithPlaceholder;

ValidateIssuerWithPlaceholder est la méthode qui valide le issuer. Dans cette méthode, il faut vérifier si le issuer du token correspond au issuer avec un placeholder qui provient des métadonnées. Pour cela, on va simplement remplacer le placeholder {tenantid} avec la valeur du claim tid du token (qui contient l'id du tenant), et vérifier que le résultat correpond au issuer du token :

private static string ValidateIssuerWithPlaceholder(
    string issuer,
    SecurityToken token,
    TokenValidationParameters parameters)
{
    // Accepts any issuer of the form
    // "https://login.microsoftonline.com/{tenantid}/v2.0",
    // where tenantid is the tid from the token.

    if (token is JwtSecurityToken jwt)
    {
        if (jwt.Payload.TryGetValue("tid", out var value) &&
            value is string tokenTenantId)
        {
            var validIssuers =
                (parameters.ValidIssuers
                    ?? Enumerable.Empty<string>())
                    .Append(parameters.ValidIssuer)
                    .Where(i => !string.IsNullOrEmpty(i));

            if (validIssuers.Any(i =>
                i.Replace("{tenantid}", tokenTenantId) == issuer))
            {
                return issuer;
            }
        }
    }

    // Recreate the exception that is thrown by default
    // when issuer validation fails

    var validIssuer = parameters.ValidIssuer ?? "null";
    var validIssuers = parameters.ValidIssuers == null
        ? "null"
        : !parameters.ValidIssuers.Any()
            ? "empty"
            : string.Join(", ", parameters.ValidIssuers);

    string errorMessage =
        $"IDX10205: Issuer validation failed. Issuer: '{issuer}'. "
        + $"Did not match: validationParameters.ValidIssuer: '{validIssuer}' "
        + $"or validationParameters.ValidIssuers: '{validIssuers}'.");

    throw new SecurityTokenInvalidIssuerException(errorMessage)
    {
        InvalidIssuer = issuer
    };
}

Une fois ceci mis en place, vous pouvez accepter des tokens de n'importe quel tenant Azure AD sans désactiver la validation du issuer.

Happy coding, et bonnes fêtes !

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus