Aserciones de cliente confidenciales

Para demostrar su identidad, las aplicaciones cliente confidenciales intercambian un secreto con Microsoft Entra ID. El secreto puede ser:

  • Un secreto de cliente (contraseña de aplicación).
  • Un certificado que se utiliza para crear una declaración firmada que contiene declaraciones estándar.

Este secreto también puede ser una aserción firmada directamente.

MSAL.NET tiene cuatro métodos para proporcionar credenciales o aserciones a la aplicación cliente confidencial:

  • .WithClientSecret()
  • .WithCertificate()
  • .WithClientAssertion()
  • .WithClientClaims()

Note

Aunque es posible usar la WithClientAssertion() API para adquirir tokens para el cliente confidencial, no se recomienda usarlo de forma predeterminada, ya que es más avanzado y está diseñado para controlar escenarios muy específicos que no son comunes. El uso de la .WithCertificate() API le permitirá MSAL.NET controlar esto por usted. Esta API le ofrece la capacidad de personalizar la solicitud de autenticación si es necesario, pero la aserción predeterminada creada por .WithCertificate() será suficiente para la mayoría de los escenarios de autenticación. Esta API también se puede usar como solución alternativa en algunos escenarios en los que MSAL.NET no pueden realizar la operación de firma internamente. La diferencia entre ambos es que usar el WithCertificate() requiere que el certificado y la clave privada estén disponibles en la máquina que crea la aserción, y usar el WithClientAssertion() permite calcular la aserción en otro lugar, como dentro de Azure Key Vault, desde Identidad administrada o con un módulo de seguridad de hardware.

Aserciones de cliente

Esto es útil si desea controlar el certificado usted mismo. Por ejemplo, si desea usar Azure API de KeyVault para la firma, lo que elimina la necesidad de descargar los certificados. Una aserción de cliente firmada tiene la forma de un JWT firmado, con la carga útil codificada en Base64 que contiene las declaraciones de autenticación requeridas exigidas por Microsoft Entra ID. O bien puede ser un JWT de otro proveedor de identidad, para el escenario de "Credencial de identidad federada".

Utiliza el delegado, que te permite calcular la aserción cada vez que MSAL necesite obtener un nuevo token del proveedor de identidad. MSAL no invoca su delegado si encuentra un token en la caché.

string signedClientAssertion = GetOrComputeAssertion();
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithClientAssertion(async (AssertionRequestOptions options) => {
                                            // use 'options.ClientID' or 'options.TokenEndpoint' to generate client assertion
                                            return await GetClientAssertionAsync(options.ClientID, options.TokenEndpoint, options.CancellationToken); 
                                          })
                                          .Build();

Las declaraciones esperadas por Microsoft Entra ID en la aserción firmada son:

Tipo de reclamación Value Description
aud https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token La declaración "aud" (audiencia) identifica a los destinatarios a los que va dirigido el JWT (en este caso, Microsoft Entra ID). Véase RFC 7519, sección 4.1.3. En este caso, ese destinatario es el punto de conexión del token del proveedor de identidades
exp 1601519414 La declaración "exp" (fecha de expiración) indica el momento en que, o después del cual, el token JWT no debe ser aceptado para su procesamiento. Consulte RFC 7519, Sección 4.1.4. Esto permite que la declaración pueda usarse hasta entonces, así que no debe durar mucho: como máximo, de 5 a 10 minutos después de nbf. Microsoft Entra ID no impone actualmente restricciones sobre el tiempo exp.
iss {ClientID} La notificación "iss" (emisor) identifica la entidad de seguridad que ha emitido el JWT; en este caso, la aplicación cliente. Use el identificador de la aplicación GUID.
jti (un GUID) La declaración "jti" (ID de JWT) proporciona un identificador único para el JWT. El valor de identificador DEBE asignarse de forma que garantice que haya una probabilidad insignificante de que el mismo valor se pueda asignar accidentalmente a un objeto de datos diferente. Si la aplicación usa varios emisores, también se deben evitar colisiones entre los valores generados por diferentes emisores. El valor "jti" es una cadena sensible a mayúsculas y minúsculas. RFC 7519, Sección 4.1.7
nbf 1601519114 La notificación "nbf" (no antes de) identifica la hora antes de la cual no debe ser aceptado el token JWT para su procesamiento. RFC 7519, Sección 4.1.5. El uso de la hora actual es adecuado.
sub {ClientID} La notificación "sub" (asunto) identifica el asunto del JWT; en este caso, también su aplicación. Use el mismo valor que iss.

Si usa un certificado como secreto de cliente, el certificado debe implementarse de forma segura. Se recomienda almacenar el certificado en un punto seguro admitido por la plataforma, como en el almacén de certificados en Windows o mediante Azure Key Vault.

Definición de la aserción

Este es un ejemplo que utiliza Microsoft.IdentityModel.JsonWebTokens para crear la aserción por usted.

        string GetSignedClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
        {                            
            // no need to add exp, nbf as JsonWebTokenHandler will add them by default.
            var claims = new Dictionary<string, object>()
            {
                { "aud", tokenEndpoint },
                { "iss", clientId },
                { "jti", Guid.NewGuid().ToString() },
                { "sub", clientId }
            };

            var securityTokenDescriptor = new SecurityTokenDescriptor
            {
                Claims = claims,
                SigningCredentials = new X509SigningCredentials(certificate)
            };

            var handler = new JsonWebTokenHandler();
            var signedClientAssertion = handler.CreateToken(securityTokenDescriptor);
        }

Como alternativa, si no desea usar Microsoft. IdentityModel.JsonWebTokens:

static string Base64UrlEncode(byte[] arg)
{
    char Base64PadCharacter = '=';
    char Base64Character62 = '+';
    char Base64Character63 = '/';
    char Base64UrlCharacter62 = '-';
    char Base64UrlCharacter63 = '_';

    string s = Convert.ToBase64String(arg);
    s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding
    s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
    s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding

    return s;
}

static string GetSignedClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
{
    // Get the RSA with the private key, used for signing.
    var rsa = certificate.GetRSAPrivateKey();

    //alg represents the desired signing algorithm, which is SHA-256 in this case
    //x5t represents the certificate thumbprint base64 url encoded
    var header = new Dictionary<string, string>()
    {
        { "alg", "PS256"},
        { "typ", "JWT" },
        { "x5t#S256", Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256))},
    };

    //Please see the previous code snippet on how to craft claims for the GetClaims() method
    var claims = GetClaims(tenantId, clientId);

    var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header);
    var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims);
    string token = Base64UrlEncode(headerBytes) + "." + Base64UrlEncode(claimsBytes);

    string signature = Base64UrlEncode(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
    string signedClientAssertion = string.Concat(token, ".", signature);
    return signedClientAssertion;
}

WithClientClaims

En algunos casos, los desarrolladores quieren incluir algunas declaraciones en las aserciones, pero aun así les gustaría que MSAL se encargara de la creación de la aserción y de la firma.

WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims = true) genera una aserción firmada que contiene las reclamaciones esperadas por Microsoft Entra ID, además de las reclamaciones adicionales del cliente que desea enviar.

string ipAddress = "192.168.1.2";
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithAuthority(new Uri(config.Authority))
                                          .WithClientClaims(certificate, 
                                                                      new Dictionary<string, string> { { "client_ip", ipAddress } })
                                          .Build();

Si una de las reclamaciones del diccionario que se pasa es igual a una de las reclamaciones obligatorias, se tiene en cuenta el valor de la reclamación adicional. Reemplaza las reclamaciones calculadas por MSAL.NET.

Si desea proporcionar sus propias declaraciones, incluidas las declaraciones obligatorias que espera Microsoft Entra ID, pase false en el parámetro mergeWithDefaultClaims.