Afirmações confidenciais do cliente

Para provar a sua identidade, as aplicações cliente confidenciais trocam um segredo com o Microsoft Entra ID. O segredo pode ser:

  • Um segredo do cliente (palavra-passe da aplicação).
  • Um certificado, que é usado para construir uma asserção assinada contendo reivindicações padrão.

Este segredo pode também ser uma afirmação assinada diretamente.

O MSAL.NET tem quatro métodos para fornecer credenciais ou asserções à aplicação cliente confidencial:

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

Note

Embora seja possível usar a WithClientAssertion() API para adquirir tokens para o cliente confidencial, não recomendamos o seu uso por defeito, pois é mais avançada e foi concebida para lidar com cenários muito específicos que não são comuns. Usar a .WithCertificate() API permitirá que o MSAL.NET trate disto por si. Esta API oferece-lhe a capacidade de personalizar o seu pedido de autenticação se necessário, mas a asserção padrão criada por .WithCertificate() será suficiente para a maioria dos cenários de autenticação. Esta API pode também ser usada como solução alternativa em alguns cenários em que o MSAL.NET falha em executar a operação de assinatura internamente. A diferença entre os dois é que usar o WithCertificate() requer que o certificado e a chave privada estejam disponíveis na máquina que cria a asserção, e usar o WithClientAssertion() permite calcular a asserção noutro local, como dentro do Azure Key Vault ou a partir do Managed Identity, ou com um módulo de segurança de hardware.

Alegações do cliente

Isto é útil se quiser tratar do certificado sozinho. Por exemplo, se quiser usar as APIs do Azure KeyVault para assinar, o que elimina a necessidade de descarregar os certificados. Uma asserção de cliente assinada assume a forma de um JWT assinado com o payload contendo as reivindicações de autenticação exigidas pelo Microsoft Entra ID, codificado em Base64. Ou pode ser um JWT de outro fornecedor de identidade, no cenário "Credencial de Identidade Federada".

Use o delegado, que lhe permite calcular a asserção sempre que a MSAL precisa de obter um novo token do fornecedor de identidade. O MSAL não invoca o seu delegado se for encontrado um token na cache.

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();

As alegações esperadas pela Microsoft Entra ID na declaração assinada são:

Tipo de afirmação Value Description
aud https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token A declaração "aud" (público-alvo) identifica os destinatários a que o JWT se destina (neste caso, o Microsoft Entra ID). Ver RFC 7519, Secção 4.1.3. Neste caso, esse destinatário é o token endpoint do provedor de identidade
exp 1601519414 A reivindicação "exp" (tempo de expiração) identifica o tempo de expiração no qual ou após o qual o JWT não deve ser aceito para processamento. Ver RFC 7519, Secção 4.1.4. Isto permite que a asserção seja utilizada até essa altura, por isso, mantenha esse período curto — no máximo, 5-10 minutos depois de nbf. O Microsoft Entra ID atualmente não impõe restrições ao exp horário.
iss {ClienteID} A declaração "iss" (emissor) identifica o principal que emitiu o JWT, neste caso, a sua aplicação cliente. Use o ID da aplicação GUID.
jti (um Guid) A reivindicação "jti" (JWT ID) fornece um identificador único para o JWT. O valor do identificador DEVE ser atribuído de forma a garantir que existe uma probabilidade negligenciável de que o mesmo valor possa ser atribuído acidentalmente a um objeto de dados diferente. Se a aplicação usar múltiplos emissores, as colisões DEVEM ser evitadas entre valores produzidos por diferentes emissores também. O valor "jti" é uma sequência de carateres que distingue maiúsculas de minúsculas. RFC 7519, Secção 4.1.7
NBF 1601519114 A reivindicação "nbf" (não antes) identifica o tempo antes do qual o JWT NÃO DEVE ser aceito para processamento. RFC 7519, Secção 4.1.5. Usar a hora atual é apropriado.
submarino {ClienteID} A declaração "sub" (subject) identifica o assunto do JWT, neste caso também a sua aplicação. Use o mesmo valor que iss.

Se usar um certificado como secreto do cliente, o certificado deve ser implementado em segurança. Recomendamos que armazene o certificado num local seguro suportado pela plataforma, como na loja de certificados no Windows ou utilizando o Azure Key Vault.

Elaboração da afirmação

Este é um exemplo que utiliza Microsoft.IdentityModel.JsonWebTokens para criar a asserção.

        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);
        }

Alternativamente, se não quiser usar a 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

Em alguns casos, os programadores querem injetar algumas reivindicações nas asserções, mas gostariam que a MSAL tratasse da criação da asserção e da assinatura.

WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims = true) produz uma asserção assinada que contém as declarações esperadas pelo Microsoft Entra ID, além de declarações adicionais do cliente que pretende 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();

Se uma das declarações no dicionário que transmite corresponder a uma das declarações obrigatórias, o valor da declaração adicional é tido em consideração. Sobrepõe-se às reivindicações calculadas pelo MSAL.NET.

Se quiser fornecer as suas próprias declarações, incluindo as declarações obrigatórias esperadas pelo Microsoft Entra ID, passe false para o parâmetro mergeWithDefaultClaims.