Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions README-Azure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Microsoft.Identity.Web.Azure

[![NuGet](https://img.shields.io/nuget/v/Microsoft.Identity.Web.Azure.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Microsoft.Identity.Web.Azure/)

This package enables ASP.NET Core web apps and web APIs to use the Azure SDKs with the Microsoft identity platform (formerly Azure AD v2.0).

## Features

- **MicrosoftIdentityTokenCredential** - Provides seamless integration between Microsoft.Identity.Web and Azure SDK's TokenCredential, enabling your application to use Azure services with Microsoft Entra ID (formerly Azure Active Directory) authentication.
- Supports both user delegated and application permission scenarios
- Works with the standard Azure SDK authentication flow

## Installation
dotnet add package Microsoft.Identity.Web.Azure
## Usage

### Basic setup

1. Register the Azure Token Credential in your service collection:

```cshap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```cshap
```cs

// In your Startup.cs or Program.cs
using Azure.Storage.Blobs;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;

public void ConfigureServices(IServiceCollection services)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using the newer style (not a separate ConfigureServices method)

{
// Register Microsoft Identity Web
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();

// Add the Azure Token Credential
services.AddMicrosoftIdentityAzureTokenCredential();

// Register Azure services
services.AddAzureClients(builder =>
{
// Use the Microsoft Identity credential for all Azure clients
builder.UseCredential(sp => sp.GetRequiredService<MicrosoftIdentityTokenCredential>());

// Configure Azure Blob Storage client
builder.AddBlobServiceClient(new Uri("https://your-storage-account.blob.core.windows.net"));
// Add other Azure clients as needed
});
}
```

### Using with Azure SDK clients

Once registered, the BlobServiceClient can be injected directly into your controllers or services:
// Direct injection into a controller or Razor Page

```csharp
[Authorize]
public class BlobController : Controller
{
private readonly BlobServiceClient _blobServiceClient;
private readonly MicrosoftIdentityTokenCredential _tokenCredential;

public BlobController(
BlobServiceClient blobServiceClient,
MicrosoftIdentityTokenCredential tokenCredential) // Optional: inject if you need to modify token behavior
{
_blobServiceClient = blobServiceClient;
_tokenCredential = tokenCredential;
}

[HttpGet]
public async Task<IActionResult> DownloadBlob(string containerName, string blobName)
{
try
{
// If you want to have get a blob on behalf of the app itself.
_tokenCredential.Options.RequestAppToken = true;
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);

// Check if blob exists
if (!await blobClient.ExistsAsync())
{
return NotFound($"Blob '{blobName}' not found in container '{containerName}'");
}

// Download the blob content
var response = await blobClient.DownloadContentAsync();
string content = response.Value.Content.ToString();

return Content(content);
}
catch (Exception ex)
{
return StatusCode(500, $"Error accessing blob: {ex.Message}");
}
}
```

For Razor Pages, you can similarly inject the client directly:

```csharp

public class BlobModel : PageModel
{
private readonly BlobServiceClient _blobServiceClient;

public BlobModel(BlobServiceClient blobServiceClient)
{
_blobServiceClient = blobServiceClient;
}

public async Task<IActionResult> OnGetAsync(string containerName, string blobName)
{
// Use the blob service client directly in your page handler
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
// ...rest of the implementation
}
}
```


### Advanced scenarios

#### Using with specific authentication schemes

If your application has multiple authentication schemes, you can specify which one to use:
// Configure the token credential to use a specific authentication scheme
tokenCredential.Options.AcquireTokenOptions.AuthenticationOptionsName = OpenIdConnectDefaults.AuthenticationScheme;

#### Custom configuration

You can customize the token acquisition behavior:
// Configure additional options
tokenCredential.Options.AcquireTokenOptions.CorrelationId = Guid.NewGuid();
tokenCredential.Options.AcquireTokenOptions.Tenant = "GUID";

## Working with older versions

This package includes two token credentials classes:
- `MicrosoftIdentityTokenCredential` (recommended)
- `TokenAcquirerTokenCredential` (deprecated)

The `TokenAcquirerTokenCredential` is marked as obsolete and is included for backward compatibility. New applications should use `MicrosoftIdentityTokenCredential` instead.

## Integration with Azure SDKs

This package enables integration with [Azure SDKs for .NET](https://learn.microsoft.com/dotnet/azure/sdk/azure-sdk-for-dotnet), including but not limited to:

- Azure Storage (Blobs, Queues, Tables, Files)
- Azure Key Vault (although you might rather want to use the DefaultCrentialLoader for credentials)
- Azure Service Bus
- Azure Cosmos DB
- Azure Event Hubs
- Azure Monitor

See [Azure SDK for .NET packages](https://learn.microsoft.com/dotnet/azure/sdk/packages#libraries-using-azurecore)
for the list of packages M

## Related packages

- [Microsoft.Identity.Web](https://www.nuget.org/packages/Microsoft.Identity.Web/)
- [Microsoft.Identity.Web.UI](https://www.nuget.org/packages/Microsoft.Identity.Web.UI/)
- [Microsoft.Identity.Web.MicrosoftGraph](https://www.nuget.org/packages/Microsoft.Identity.Web.MicrosoftGraph/)

## Learn more

- [Microsoft Identity Web documentation](https://aka.ms/ms-identity-web)
- [Azure SDK documentation](https://docs.microsoft.com/azure/developer/azure-sdk/)
- [Microsoft identity platform documentation](https://docs.microsoft.com/azure/active-directory/develop/)
1 change: 1 addition & 0 deletions src/Microsoft.Identity.Web.Azure/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

[assembly: SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "This method has an async counterpart.", Scope = "member", Target = "~M:Microsoft.Identity.Web.TokenAcquirerAppTokenCredential.GetToken(Azure.Core.TokenRequestContext,System.Threading.CancellationToken)~Azure.Core.AccessToken")]
[assembly: SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "This method has an async counterpart.", Scope = "member", Target = "~M:Microsoft.Identity.Web.TokenAcquirerTokenCredential.GetToken(Azure.Core.TokenRequestContext,System.Threading.CancellationToken)~Azure.Core.AccessToken")]
[assembly: SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "This method has an async counterpart.", Scope = "member", Target = "~M:Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.GetToken(Azure.Core.TokenRequestContext,System.Threading.CancellationToken)~Azure.Core.AccessToken")]
1 change: 1 addition & 0 deletions src/Microsoft.Identity.Web.Azure/InternalAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.MicrosoftIdentityTokenCredential(Microsoft.Identity.Abstractions.ITokenAcquirerFactory! tokenAcquirerFactory, Microsoft.Identity.Web.ITokenAcquisitionHost! tokenAcquisitionHost) -> void
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>Microsoft Identity Web.Azure</Title>
<Product>Microsoft Identity Web.Azure</Product>
Expand All @@ -8,9 +8,9 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md">
<None Include="..\..\README-Azure.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<PackagePath>\README.md</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Microsoft.Identity.Abstractions;

namespace Microsoft.Identity.Web
{
/// <summary>
/// Azure SDK token credential for tokens based on the <see cref="IAuthorizationHeaderProvider"/>
/// service.
/// </summary>
public class MicrosoftIdentityTokenCredential : TokenCredential
{
private ITokenAcquirerFactory _tokenAcquirerFactory;
private readonly IAuthenticationSchemeInformationProvider _authenticationSchemeInformationProvider;

/// <summary>
/// Constructor from an ITokenAcquisition service.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't clear to me

/// </summary>
/// <param name="tokenAcquirerFactory">Token acquisition factory</param>
/// <param name="authenticationSchemeInformationProvider">Host for the token acquisition</param>
public MicrosoftIdentityTokenCredential(ITokenAcquirerFactory tokenAcquirerFactory, IAuthenticationSchemeInformationProvider authenticationSchemeInformationProvider)
{
_tokenAcquirerFactory = tokenAcquirerFactory ?? throw new System.ArgumentNullException(nameof(tokenAcquirerFactory));
_authenticationSchemeInformationProvider = authenticationSchemeInformationProvider ?? throw new System.ArgumentNullException(nameof(authenticationSchemeInformationProvider));
}

AuthorizationHeaderProviderOptions _options = new AuthorizationHeaderProviderOptions();

/// <summary>
/// Options used to configure the token acquisition behavior.
/// </summary>
public AuthorizationHeaderProviderOptions Options => _options;

/// <inheritdoc/>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
ITokenAcquirer tokenAcquirer = _tokenAcquirerFactory.GetTokenAcquirer(_authenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(_options.AcquireTokenOptions.AuthenticationOptionsName));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ITokenAcquirer tokenAcquirer = _tokenAcquirerFactory.GetTokenAcquirer(_authenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(_options.AcquireTokenOptions.AuthenticationOptionsName));
ITokenAcquirer tokenAcquirer = _tokenAcquirerFactory.GetTokenAcquirer(_authenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(Options.AcquireTokenOptions.AuthenticationOptionsName));

if (Options.RequestAppToken)
{
AcquireTokenResult result = tokenAcquirer.GetTokenForAppAsync(requestContext.Scopes.First(), cancellationToken: cancellationToken)
.GetAwaiter()
.GetResult();
return new AccessToken(result.AccessToken!, result.ExpiresOn);
}
else
{
AcquireTokenResult result = tokenAcquirer.GetTokenForUserAsync(requestContext.Scopes, Options.AcquireTokenOptions, cancellationToken: cancellationToken)
.GetAwaiter()
.GetResult();
return new AccessToken(result.AccessToken!, result.ExpiresOn);
}
}

/// <inheritdoc/>
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
ITokenAcquirer tokenAcquirer = _tokenAcquirerFactory.GetTokenAcquirer(_authenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(_options.AcquireTokenOptions.AuthenticationOptionsName));
if (Options.RequestAppToken)
{
AcquireTokenResult result = await tokenAcquirer.GetTokenForAppAsync(requestContext.Scopes.First(), cancellationToken: cancellationToken);
return new AccessToken(result.AccessToken!, result.ExpiresOn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a comment describing why the null suppression is safe.

}
else
{
AcquireTokenResult result = await tokenAcquirer.GetTokenForUserAsync(requestContext.Scopes, Options.AcquireTokenOptions, cancellationToken: cancellationToken);
return new AccessToken(result.AccessToken!, result.ExpiresOn);
}
}
}
}
7 changes: 7 additions & 0 deletions src/Microsoft.Identity.Web.Azure/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
#nullable enable
Microsoft.Identity.Web.MicrosoftIdentityTokenCredential
Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.MicrosoftIdentityTokenCredential(Microsoft.Identity.Abstractions.ITokenAcquirerFactory! tokenAcquirerFactory, Microsoft.Identity.Web.IAuthenticationSchemeInformationProvider! authenticationSchemeInformationProvider) -> void
Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.Options.get -> Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions!
Microsoft.Identity.Web.ServiceCollectionExtensionForAzureCreds
override Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken) -> Azure.Core.AccessToken
override Microsoft.Identity.Web.MicrosoftIdentityTokenCredential.GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<Azure.Core.AccessToken>
static Microsoft.Identity.Web.ServiceCollectionExtensionForAzureCreds.AddMicrosoftIdentityAzureTokenCredential(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Identity.Web
{
/// <summary>
/// Extensin methods for adding Azure credentials to the service collection.
/// </summary>
public static class ServiceCollectionExtensionForAzureCreds
{
/// <summary>
/// Enables apps to use the <see cref="MicrosoftIdentityTokenCredential"/> for Azure AD authentication.
/// </summary>
/// <param name="services">Service collection where to add the <see cref="MicrosoftIdentityTokenCredential"/>.</param>
/// <returns>the service collection.</returns>
public static IServiceCollection AddMicrosoftIdentityAzureTokenCredential(this IServiceCollection services)
{
services.AddScoped<MicrosoftIdentityTokenCredential>();
return services;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -12,6 +13,7 @@ namespace Microsoft.Identity.Web
/// <summary>
/// Azure SDK token credential for App tokens based on the ITokenAcquisition service.
/// </summary>
[Obsolete("Rather use MicrosoftIdentityTokenCredential.")]
public class TokenAcquirerAppTokenCredential : TokenCredential
{
private ITokenAcquirer _tokenAcquirer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
Expand All @@ -11,6 +12,7 @@ namespace Microsoft.Identity.Web
/// <summary>
/// Azure SDK token credential based on the ITokenAcquisition service.
/// </summary>
[Obsolete("Rather use MicrosoftIdentityTokenCredential.")]
public class TokenAcquirerTokenCredential : TokenCredential
{
private ITokenAcquirer _tokenAcquirer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Web
{
/// <summary>
/// Provides information about the effective authentication scheme. If passing null
/// or string.Empty, this returns the default authentication scheme.
/// </summary>
public interface IAuthenticationSchemeInformationProvider

{
/// <summary>
/// Get the effective authentication scheme based on the provided authentication scheme.
/// </summary>
/// <param name="authenticationScheme">intended authentication scheme.</param>
/// <returns>Effective authentication scheme (default authentication scheme if the intended
/// authentication scheme is null or an empty string.</returns>
string GetEffectiveAuthenticationScheme(string? authenticationScheme);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

namespace Microsoft.Identity.Web
{
internal interface ITokenAcquisitionHost

internal interface ITokenAcquisitionHost : IAuthenticationSchemeInformationProvider
{
MergedOptions GetOptions(string? authenticationScheme, out string effectiveAuthenticationScheme);

void SetSession(string key, string value);
string GetEffectiveAuthenticationScheme(string? authenticationScheme);
string? GetCurrentRedirectUri(MergedOptions mergedOptions);
SecurityToken? GetTokenUsedToCallWebAPI();
Task<ClaimsPrincipal?> GetAuthenticatedUserAsync(ClaimsPrincipal? user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClientBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraph, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraphBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Azure, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.Identity.Web.IAuthenticationSchemeInformationProvider
Microsoft.Identity.Web.IAuthenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(string? authenticationScheme) -> string!
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.Identity.Web.IAuthenticationSchemeInformationProvider
Microsoft.Identity.Web.IAuthenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(string? authenticationScheme) -> string!
Loading
Loading