Skip to content

Custom Signed Assertion Providers

JLoze edited this page Feb 7, 2025 · 8 revisions

Microsoft Identity Web allows the use of custom credential sources/identity providers by enabling extension of the built-in credential loaders with your own custom SDK implementation. You bring the signed credential; we do the rest.

A working sample can be found here in the Microsoft Identity Web repo which doubles as an integration test for this feature.

Creating Your Custom Extension

Building an extension to go on top of Microsoft Identity Web can be done with just three steps:

Step 1: Create an assertion provider class deriving from ClientAssertionProviderBase.

  • The assertion provider takes in the properties and options needed to request a signed assertion from your custom source.
  • The assertion provider then receives the signed assertion from your custom source and returns it.
  • The sample file can be found here.

Example:

 namespace MyCustomExtension

 {
     internal class MyCustomSignedAssertionProvider : ClientAssertionProviderBase
     {
         public MyCustomSignedAssertionProvider(Dictionary<string, object>? properties)
         {
             // Here you would implement the logic to extract what you need from the properties passed in
             // the configuration
         }
         protected override Task<ClientAssertion> GetClientAssertionAsync(AssertionRequestOptions? assertionRequestOptions)
         {
             // Here you would implement the logic to get the signed assertion, which is probably going
             // to be a call to a service. This call can be parameterized by the parameters in the properties
             // of the constructor.

             // In this sample code we just create an empty signed assertion and return it.
             var clientAssertion = new ClientAssertion("FakeAssertion", DateTimeOffset.Now);
             return Task.FromResult(clientAssertion);
         }
     }
 }

Step 2: Provide a signed assertion loader class ironically implementing ICustomSignedAssertionProvider.

  • Your custom assertion loader will use instances of your custom assertion provider to get signed assertions from your custom source.
  • Your loader will be called from the Microsoft Identity Web library through the LoadIfNeededAsync method for relevant credential descriptions.
  • Your loader will need to call GetSignedAssertionAsync from your provider in order to get the signed assertion.
  • If you successfully obtain a signed assertion, set the CredentialDescription.CachedValue to the new MyCustomSignedAssertionProvider.
  • If there is an exception, set the skip value in the credential description to true to avoid trying to get the same credential again.
  • It is ok to not get a credential, not all credentials are available in all contexts a given program runs in.
  • Important: only throw the exception if you want the Microsoft Identity Web library to also throw the exception, likely crashing the program.
  • The sample file can be found here.

Example:

 namespace MyCustomExtension

 {
     internal class MyCustomSignedAssertionLoader : ICustomSignedAssertionProvider
     {
         public MyCustomSignedAssertionLoader(ILogger<DefaultCredentialsLoader> logger)
         {
             _logger = logger;
         }
         public CredentialSource CredentialSource => CredentialSource.CustomSignedAssertion;
 
         public string Name => "MyCustomExtension";
 
         private ILogger<DefaultCredentialsLoader> _logger;
 
         public async Task LoadIfNeededAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters = null)
         {
             MyCustomSignedAssertionProvider? signedAssertion = credentialDescription.CachedValue as MyCustomSignedAssertionProvider;
             if (credentialDescription.CachedValue == null)
             {
                 signedAssertion = new MyCustomSignedAssertionProvider(credentialDescription.CustomSignedAssertionProviderData);
             }
             try
             {
                 // Given that not all credentials are available in all contexts (like managed identities not being available on local machines),
                 // we need to attempt to get a signed assertion, but if it fails, keep trying to find a working credential.
                 _ = await signedAssertion!.GetSignedAssertionAsync(null);

                 // Be sure to set the CachedValue in the CredentialDescription object to your signed assertion so you don't reevaluate
                 // the same credential more than is necessary.
                 credentialDescription.CachedValue = signedAssertion;
             }
             catch (Exception)
             {
                 // Setting the skip to true will tell the program to no longer try loading credentials 
                 // from this specific CredentialDescription object instance.
                 credentialDescription.Skip = true;

                 // Only throw the Exception if you want it to also be thrown by the Microsoft Identity Web library.
                 // Use the logger if you only want to log it.
                 throw;
             }
         }
     }
 }

Step 3: Write an IServiceCollection extension method to register the custom loader from step 2.

  • This is what enables Microsoft Identity Web to find your custom loader
  • This is the only thing a developer using your extension will need to call in order to use it
  • The sample file can be found here.

Example:

   public static IServiceCollection AddCustomSignedAssertionProvider(
      this IServiceCollection services)
  {
      services.AddSingleton<ICustomSignedAssertionProvider, MyCustomSignedAssertionLoader>();
      return services;
  }

Using Your Custom Extension

For a user to use your extension two things need to happen.

First: the correct configuration details need to be in place.

  • In the example below this is done using the call to tokenAcquirerFactory.Services.Configure
  • This can instead be done in the appsettings.json which you can find an example of in the sample here

Second: call your custom IServiceCollection extension from the TokenAcquirerFactory

  • Be sure to call it before building the factory.
  • In the sample this is done in the test file here
         TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
         tokenAcquirerFactory.Services.Configure<MicrosoftIdentityApplicationOptions>(options =>
         {
             options.Instance = "https://login.microsoftonline.com/";
             options.TenantId = "msidlab4.onmicrosoft.com";
             options.ClientId = "f6b698c0-140c-448f-8155-4aa9bf77ceba";
             options.ClientCredentials = [ new CredentialDescription() { SourceType = CredentialSource.CustomSignedAssertion,
             CustomSignedAssertionProviderName = "MyCustomExtension"}];
         });
         tokenAcquirerFactory.Services.AddCustomSignedAssertionProvider();
         var serviceProvider = tokenAcquirerFactory.Build();

Getting started with Microsoft Identity Web

Token cache serialization

Web apps

Web APIs

Daemon scenario

Advanced topics

Extensibility

Credential providers

FAQ

News

Contribute

Other resources

Clone this wiki locally