Skip to content
This repository has been archived by the owner on Jun 30, 2023. It is now read-only.

Client credential flows

Bogdan Gavril edited this page Nov 19, 2020 · 10 revisions

There are three types of client secrets in ADAL.NET:

  • Application secrets
  • Certificates
  • Optimized client assertions

Client Credentials with application secret in ADAL.NET

The client credential flow with an application secret (a kind of password) in ADAL.NET is well described in the following sample: https://github.com/Azure-Samples/active-directory-dotnet-daemon During the registration of a the confidential client application with AAD, a client secret is generated (a kind of application password). When the client wants to acquire a token in its own name it will:

  • instantiate a ClientCredential class, passing both the ClientId of the application and the client secret (in clear or as a SecureClientSecret which is built from a .NET secure string). See code here:
clientCredential = new ClientCredential(clientId, appKey);

image

On the .NET Framework 4.5 platform, the ISecureClientSecret interface is implemented by the SecureClientSecret class which can be built from a SecureString. However SecureString is not available on the other platforms, and therefore, on these platforms no implementation of the interface is provided:

image

  • Call the override of AcquireTokenAsync taking as parameters the resourceId and a ClientCredential instance like in this sample.
AuthenticationContext authenticationContext =
  new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
   AuthenticationResult result = 
      await authenticationContext.AcquireTokenAsync("https://resourceUrl",
                                                    clientCredential);

This API benefits from the cache automatically, so no need to call AcquireTokenSilent first

Client Credentials with certificate in ADAL.NET

Using the client credential flow with certificate in ADAL.NET is well described the following sample: https://github.com/Azure-Samples/active-directory-dotnet-daemon-certificate-credential. This time, when the application is registered with AzureAD, it uploads the public key of a certificate. When it wants to acquire a token, the client application will instantiate the ClientAssertionCertificate class instead of a ClientCredential instance, and pass it to the override of AcquireTokenAsync which takes a IClientAssertionCertificate as a second parameter, after the resource. The steps are:

  • Instanciate a ClientAssertionCertificate passing the application Id and a X509Certificate2. For more details, see the code here
certCred = new ClientAssertionCertificate(clientId, cert);

image

  • Call AcquireTokenAsync passing the client assertion certificate instance. For more details, see the code here
AuthenticationContext authenticationContext =
  new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
   AuthenticationResult result = 
      await authenticationContext.AcquireTokenAsync("https://resourceUrl",
                                                    certCred);

ClientAssertion in ADAL.NET

There is another way for an application to prove its identity, providing a client assertion, using the ClientAssertion class. And there are overrides of AcquireTokenAsync taking client assertions. One might wonder why have the ClientAssertion class whereas the ClientCredentials class already exists for application secret and the ClientAssertionCertificate is used for the certificate case.

image

This is actually because:

  • Cryptographic operations are very expensive, and when using ClientAssertionCertificate, they need to happen each time there is a call to AzureAD.
  • Also, some developers don't feel comfortable passing a certificate to ADAL.NET. Therefore, they have the possibility of creating a client assertion themselves, and passing it to ADAL.NET

Here is an example of how to craft these claims:

Option 1 - using Microsoft.IdentityModel.JsonWebTokens library (Recommended)

You have the option of using Microsoft.IdentityModel.JsonWebTokens to create the assertion for you.

         private static string GetSignedClientAssertionUsingWilson(
            string issuer, // client ID
            string aud, // $"{authority}/oauth2/token" 
            X509Certificate2 cert)
        {
            // no need to add exp, nbf as JsonWebTokenHandler will add them by default.
            var claims = new Dictionary<string, object>()
            {
                { "aud", aud },
                { "iss", issuer },
                { "jti", Guid.NewGuid().ToString() },
                { "sub", issuer }
            };

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

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

            return signedClientAssertion;
        }

Option 2 - creating a signed assertion manually

         private string GetSignedClientAssertionDirectly(
            string issuer, // client ID
            string audience, // ${authority}/oauth2/token
            X509Certificate2 certificate)
        {
            const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
            DateTime validFrom = DateTime.UtcNow;
            var nbf = ConvertToTimeT(validFrom);
            var exp = ConvertToTimeT(validFrom + TimeSpan.FromSeconds(JwtToAadLifetimeInSeconds));

            var payload = new Dictionary<string, string>()
            {
                { "aud", audience },
                { "exp", exp.ToString(CultureInfo.InvariantCulture) },
                { "iss", issuer },
                { "jti", Guid.NewGuid().ToString() },
                { "nbf", nbf.ToString(CultureInfo.InvariantCulture) },
                { "sub", issuer }
            };

            RSACng rsa = certificate.GetRSAPrivateKey() as RSACng;

            //alg represents the desired signing algorithm, which is SHA-256 in this case
            //kid represents the certificate thumbprint
            var header = new Dictionary<string, string>()
            {
              { "alg", "RS256"},
              { "kid", Base64UrlEncode(certificate.GetCertHash()) }
            };

            string token = Base64UrlEncode(
                Encoding.UTF8.GetBytes(JObject.FromObject(header).ToString())) + 
                "." + 
                Base64UrlEncode(Encoding.UTF8.GetBytes(JObject.FromObject(payload).ToString()));

            string signature = Base64UrlEncode(
                rsa.SignData(
                    Encoding.UTF8.GetBytes(token),
                    HashAlgorithmName.SHA256, 
                    System.Security.Cryptography.RSASignaturePadding.Pkcs1));
            return string.Concat(token, ".", signature);
        }

        private 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;
        }        
       
        private static long ConvertToTimeT(DateTime time)
        {
            var startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            TimeSpan diff = time - startTime;
            return (long)diff.TotalSeconds;
        }

In order to reduce the amount of overhead needed to perform this authentication, it is recommended to cache the assertion for the duration of the expiration time. The value of JwtToAadLifetimeInSeconds above can be adjusted to the desired expiration time of the assertion. It is in milliseconds and is set to 10 minutes which is what MSAL.NET uses internally by default.

Samples illustrating acquiring tokens with the Client Credential flow

Sample Platform Description
active-directory-dotnet-daemon Desktop (Console) A Windows console application that calls a web API using its app identity (instead of a user's identity) to get access tokens in an unattended job or process.
active-directory-dotnet-daemon-certificate-credential Desktop (Console) A .NET 4.5 daemon application that uses a certificate to authenticate with Azure AD and get OAuth 2.0 access tokens.
active-directory-dotnetcore-daemon-certificate-credential .NET Core 2.0 desktop (Console) The same as the previous sample, but as a .NET Core 2.0 app (and not a .NET Framework app).
Clone this wiki locally