-
Notifications
You must be signed in to change notification settings - Fork 214
Client credential flows
There are three types of client secrets in ADAL.NET:
- Application secrets
- Certificates
- Optimized client assertions
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 theClientId
of the application and the client secret (in clear or as aSecureClientSecret
which is built from a .NET secure string). See code here:
clientCredential = new ClientCredential(clientId, appKey);
On the .NET Framework 4.5 platform, the
ISecureClientSecret
interface is implemented by theSecureClientSecret
class which can be built from aSecureString
. However SecureString is not available on the other platforms, and therefore, on these platforms no implementation of the interface is provided:
- Call the override of AcquireTokenAsync taking as parameters the
resourceId
and aClientCredential
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
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);
- 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);
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.
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:
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;
}
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.
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). |
- Home
- Why use ADAL.NET?
- Register your app with AAD
- AuthenticationContext
- Acquiring Tokens
- Calling a protected API
- Acquiring a token interactively
- Acquiring tokens silently
- Using Device Code Flow
- Using Embedded Webview and System Browser in ADAL.NET and MSAL.NET
- With no user
- In the name of a user
- on behalf of (Service to service calls)
- by authorization code (Web Apps)
- Use async controller actions
- Exception types
- using Broker on iOS and Android
- Logging
- Token Cache serialization
- User management
- Using ADAL with a proxy
- Authentication context in multi-tenant scenarios
- Troubleshooting MFA in a WebApp or Web API
- Provide your own HttpClient
- iOS Keychain Access