-
Notifications
You must be signed in to change notification settings - Fork 219
Custom Signed Assertion Providers
JLoze edited this page Feb 7, 2025
·
8 revisions
Microsoft Identity Web allows the use of custom credential sources providers by enabling extensions 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 (The test file would not be part of the extension, but shows the code that developers using your extension would write to use it)
Building an extension to go on top of Microsoft Identity Web can be done with just three steps (3 files likely to be added to a new project: your extension project):
Step 1: Create an assertion provider class deriving from ClientAssertionProviderBase.
- Your assertion provider takes in the properties and options needed to request a signed assertion from your custom source.
- Your assertion provider then receives the signed assertion from your custom source and returns it. Microsoft.Identity.Web takes care of caching.
- 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. You could have other parameters to your constructor too (see below)
}
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 a fake signed assertion and return it, with its expiry
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 (for instance Managed Identity is only available on Azure machines)
- 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;
}
}
}
}
- 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:
namespace MyCustomExtension
{
public static class CustomSignedAssertionProviderExtensions
{
public static IServiceCollection AddCustomSignedAssertionProvider(
this IServiceCollection services)
{
services.AddSingleton<ICustomSignedAssertionProvider, MyCustomSignedAssertionLoader>();
return services;
}
}
}
For a user to use your extension two things need to happen.
- 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 (in the service initialization on ASP.NET Core or from the TokenAcquirerFactory.Services elsewhere)
- If you are not on ASP.NET Core, be sure to call it before building the TokenAcquirerFactory.
- Never use the TokenAcquirerFactory on ASP.NET Core: you already have a service collection.
- In the sample this is done as part of an end to end test 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",
CustomSignedAssertionProviderData = { {"key", "value"} }
}
];
});
tokenAcquirerFactory.Services.AddCustomSignedAssertionProvider();
var serviceProvider = tokenAcquirerFactory.Build();
- Home
- Why use Microsoft Identity Web?
- Web apps
- Web APIs
- Using certificates
- Minimal support for .NET FW Classic
- Logging
- Azure AD B2C limitations
- Samples
- Web apps
- Web app samples
- Web app template
- Call an API from a web app
- Managing incremental consent and conditional access
- Web app troubleshooting
- Deploy to App Services Linux containers or with proxies
- SameSite cookies
- Hybrid SPA
- Web APIs
- Web API samples
- Web API template
- Call an API from a web API
- Token Decryption
- Web API troubleshooting
- web API protected by ACLs instead of app roles
- gRPC apps
- Azure Functions
- Long running processes in web APIs
- Authorization policies
- Generic API
- Customization
- Logging
- Calling graph with specific scopes/tenant
- Multiple Authentication Schemes
- Utility classes
- Setting FIC+MSI
- Mixing web app and web API
- Deploying to Azure App Services
- Azure AD B2C issuer claim support
- Performance
- specify Microsoft Graph scopes and app-permissions
- Integrate with Azure App Services authentication
- Ajax calls and incremental consent and conditional access
- Back channel proxys
- Client capabilities