|
| 1 | +# Using the ClientCertificateCredential |
| 2 | + |
| 3 | +Applications which execute in a protected environment can authenticate using a client assertion signed by a private key whose public key or root certificate is registered with AAD. The Azure.Identity library provides the `ClientCertificateCredential` for applications choosing to authenticate this way. Below are some examples of how applications can utilize the `ClientCertificateCredential` to authenticate clients. |
| 4 | + |
| 5 | + |
| 6 | +## Loading certificates from disk |
| 7 | + |
| 8 | +Applications commonly need to load a client certificate from disk. One approach is for the application to construct the `ClientCertificateCredential` by specifying the applications tenant id, client id, and the path to the certificate. |
| 9 | + |
| 10 | +```C# Snippet:Identity_CertificateCredenetial_CreateWithPath |
| 11 | +var credential = new ClientCertificateCredential(tenantId, clientId, "./certs/cert.pfx"); |
| 12 | +``` |
| 13 | +Alternatively, the application can construct the `X509Certificate2` themselves, such as in the following example, where the certificate key is password protected. |
| 14 | + |
| 15 | +```C# Snippet:Identity_CertificateCredenetial_CreateWithX509Cert |
| 16 | +var certificate = new X509Certificate2("./certs/cert-password-protected.pfx", "password"); |
| 17 | + |
| 18 | +var credential = new ClientCertificateCredential(tenantId, clientId, certificate); |
| 19 | +``` |
| 20 | + |
| 21 | +## Loading certificates from an X509Store |
| 22 | + |
| 23 | +Applications running on platforms which provide a secure certificate store might prefer to store and retrieve certificates from there. While the `ClientCertificateCredential` doesn't directly provide a mechanism for this, the application can retrieve the appropriate certificate from the store and use it to construct the `ClientCertificateCredential`. |
| 24 | + |
| 25 | +Consider the scenario where a pinned certificate used for development authentication is stored in the Personal certificate store. Since the certificate is pinned it can be identified by its thumbprint, which the application might read from configuration or the environment. |
| 26 | + |
| 27 | +```C# Snippet:Identity_CertificateCredenetial_CreateFromStore |
| 28 | +using var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); |
| 29 | + |
| 30 | +store.Open(OpenFlags.ReadOnly); |
| 31 | + |
| 32 | +var certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(cert => cert.Thumbprint == thumbprint); |
| 33 | + |
| 34 | +var credential = new ClientCertificateCredential(tenantId, clientId, certificate); |
| 35 | +``` |
| 36 | + |
| 37 | +## Rolling Certificates |
| 38 | + |
| 39 | +Long running applications may have the need to roll certificates during process execution. Certificate rotation is not currently supported by the `ClientCertficateCredential` which treats the certificate used to construct the credential as immutable. This means that any clients constructed with an `ClientCertificateCredential` using a particular cert would fail to authenticate requests after that cert has been rolled and the original is no longer valid. |
| 40 | + |
| 41 | +However, if an application wants to roll this certificate without creating new service clients, it can accomplish this by creating its own `TokenCredential` implementation which wraps the `ClientCertificateCredential`. The implementation of this custom credential `TokenCredential` would somewhat depend on how the application handles certificate rotation. |
| 42 | + |
| 43 | +### Explicit rotation |
| 44 | + |
| 45 | +If the application get's notified of certificate rotations and it can directly respond, it might choose to wrap the `ClientCertificateCredential` in a custom credential which provides a means for rotating the certificate. |
| 46 | + |
| 47 | +```C# Snippet:Identity_CertificateCredenetial_RotatableCredential |
| 48 | +public class RotatableCertificateCredential : TokenCredential |
| 49 | +{ |
| 50 | + private readonly string _tenantId; |
| 51 | + private readonly string _clientId; |
| 52 | + private ClientCertificateCredential _credential; |
| 53 | + |
| 54 | + public RotatableCertificateCredential(string tenantId, string clientId, X509Certificate2 certificate) |
| 55 | + { |
| 56 | + _tenantId = tenantId; |
| 57 | + _clientId = clientId; |
| 58 | + _credential = new ClientCertificateCredential(_tenantId, _clientId, certificate); |
| 59 | + } |
| 60 | + |
| 61 | + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) |
| 62 | + { |
| 63 | + return _credential.GetToken(requestContext, cancellationToken); |
| 64 | + } |
| 65 | + |
| 66 | + public async override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) |
| 67 | + { |
| 68 | + return await _credential.GetTokenAsync(requestContext, cancellationToken); |
| 69 | + } |
| 70 | + |
| 71 | + public void RotateCertificate(X509Certificate2 certificate) |
| 72 | + { |
| 73 | + _credential = new ClientCertificateCredential(_tenantId, _clientId, certificate); |
| 74 | + } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +The above example shows a custom credential type `RotatableCertificateCredential` which provides a `RotateCertificateMethod`. The implementation internally relies on a `ClientCertificateCredential` instance `_credential`, and `RotateCertificate` simply replaces this instance with a new instance using the updated certificate. |
| 79 | + |
| 80 | +### Implicit rotation |
| 81 | +Some applications might want to respond to certificate rotations which are external to the application, for instance a separate process rotates the certificate by updating it on disk. Here the application create a custom credential which checks for certificate updates when tokens are requested. |
| 82 | + |
| 83 | +```C# Snippet:Identity_CertificateCredenetial_RotatingCredential |
| 84 | +public class RotatingCertificateCredential : TokenCredential |
| 85 | +{ |
| 86 | + private readonly string _tenantId; |
| 87 | + private readonly string _clientId; |
| 88 | + private readonly string _path; |
| 89 | + private readonly object _refreshLock = new object(); |
| 90 | + private DateTimeOffset _credentialLastModified; |
| 91 | + private ClientCertificateCredential _credential; |
| 92 | + |
| 93 | + public RotatingCertificateCredential(string tenantId, string clientId, string path) |
| 94 | + { |
| 95 | + _tenantId = tenantId; |
| 96 | + _clientId = clientId; |
| 97 | + _path = path; |
| 98 | + |
| 99 | + RefreshCertificate(); |
| 100 | + } |
| 101 | + |
| 102 | + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) |
| 103 | + { |
| 104 | + RefreshCertificate(); |
| 105 | + |
| 106 | + return _credential.GetToken(requestContext, cancellationToken); |
| 107 | + } |
| 108 | + |
| 109 | + public async override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) |
| 110 | + { |
| 111 | + RefreshCertificate(); |
| 112 | + |
| 113 | + return await _credential.GetTokenAsync(requestContext, cancellationToken); |
| 114 | + } |
| 115 | + |
| 116 | + public void RefreshCertificate() |
| 117 | + { |
| 118 | + lock (_refreshLock) |
| 119 | + { |
| 120 | + var certificateLastModified = File.GetLastWriteTimeUtc(_path); |
| 121 | + |
| 122 | + if (_credentialLastModified < certificateLastModified) |
| 123 | + { |
| 124 | + _credential = new ClientCertificateCredential(_tenantId, _clientId, new X509Certificate2(_path)); |
| 125 | + |
| 126 | + _credentialLastModified = certificateLastModified; |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +In this example the custom credential type `RotatingCertifiateCredential` again uses a `ClientCertificateCredential` instance `_credential` to retrieve tokens. However, in this case it will attempt to refresh the certificate prior to obtaining the token. The method `RefreshCertificate` will query to see if the certificate has changed, and if so it will replace the instance `_credential` with a new instance using the new certificate. |
0 commit comments