Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace AzureADAppManagement
public class AppCertificateManager
public async Task RollCertificatesAsync(string appId, string appObjectId, string tenantId, X509Certificate2 existingCertificate, X509Certificate2 newCertificate)
var graph = GetGraphClient(() => GetTokenAsync(appId, existingCertificate, tenantId));
var keyCredential = CreateKeyCredential(newCertificate);
var proof = GetJwtTokenProof(existingCertificate, appId);
await graph.Applications[appObjectId].AddKey(keyCredential, proof).Request().PostAsync();
// Waiting 120 secs; proceeding immediately may result in failure if the new cert is not fully processed server side
// Not sure how much time is appropriate here, to be honest. 120 secs seems excessive, but 20 secs for example has proven too short
await Task.Delay(120000);
// Re-init the client to use the new cert for token retrieval
graph = GetGraphClient(() => GetTokenAsync(appId, newCertificate, tenantId));
// Find the keyId for the old certificate
var app = await graph.Applications[appObjectId].Request().GetAsync();
var keyId = app.KeyCredentials.Single(key =>
return Convert.ToBase64String(key.CustomKeyIdentifier).Equals(existingCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase);
// Create new proof based on the new certificate
proof = GetJwtTokenProof(newCertificate, appId);
// Remove the old certificate
await graph.Applications[appObjectId].RemoveKey(keyId.Value, proof).Request().PostAsync();
private GraphServiceClient GetGraphClient(Func<Task<string>> tokenDelegate)
return new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
var token = await tokenDelegate();
.Authorization = new AuthenticationHeaderValue("bearer", token);
private async Task<string> GetTokenAsync(string appId, X509Certificate2 certificate, string tenantId)
var app = ConfidentialClientApplicationBuilder.Create(appId)
var scopes = new[] { "" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
private string GetJwtTokenProof(X509Certificate2 signingCert, string appId)
var notBefore = DateTime.Now;
var expires = notBefore.AddMinutes(10);
var handler = new JwtSecurityTokenHandler();
var credentials = new X509SigningCredentials(signingCert);
var jwtToken = handler.CreateJwtSecurityToken(appId, "", null, notBefore, expires, null, credentials);
return handler.WriteToken(jwtToken);
private KeyCredential CreateKeyCredential(X509Certificate2 certificate)
return new KeyCredential()
Key = certificate.RawData,
Usage = "Verify",
Type = "AsymmetricX509Cert"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment