Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Bug fixes
- Move Credential Guard key attestation into the optional `Microsoft.Identity.Web.KeyAttestation` package. Managed identity mTLS proof-of-possession uses the unattested flow unless the package is installed and `AddMicrosoftIdentityWebKeyAttestation()` is registered, preventing native key-attestation binaries and symbols from being published with unrelated applications.
- OWIN web APIs now reject bearer tokens that contain neither scope nor role claims by default, matching ASP.NET Core behavior and preventing unprovisioned app-only tokens from authenticating. Applications using ACL-based authorization must set `AllowWebApiToBeAuthorizedByACL` to `true`.

## 4.14.2

Expand Down
48 changes: 45 additions & 3 deletions src/Microsoft.Identity.Web.OWIN/AppBuilderExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Globalization;
using System.Linq;
using System.Security.Authentication;
using System.Security.Claims;
using System.Threading.Tasks;
Expand Down Expand Up @@ -38,6 +39,11 @@ public static class AppBuilderExtension
/// <param name="updateOptions">Update the OWIN options if you want to finesse the token validation.</param>
/// <param name="configurationSection">Configuration section in which to read the options.</param>
/// <returns>The app builder to chain.</returns>
/// <remarks>
/// By default, bearer tokens must contain a scope or role claim. Set
/// <see cref="Microsoft.Identity.Abstractions.IdentityApplicationOptions.AllowWebApiToBeAuthorizedByACL"/> to
/// <c>true</c> when the application uses ACL-based authorization instead.
/// </remarks>
public static IAppBuilder AddMicrosoftIdentityWebApi(
this IAppBuilder app,
OwinTokenAcquirerFactory tokenAcquirerFactory,
Expand All @@ -49,12 +55,12 @@ public static IAppBuilder AddMicrosoftIdentityWebApi(
var configuration = tokenAcquirerFactory.Configuration;

// Configure the Microsoft authentication options
services.Configure(
string.Empty,
Action<MicrosoftIdentityApplicationOptions> configureOptions =
configureMicrosoftIdentityApplicationOptions ?? (option =>
{
configuration?.GetSection(configurationSection).Bind(option);
}));
});
services.Configure(string.Empty, configureOptions);
services.Configure<ConfidentialClientApplicationOptions>(
string.Empty,
(option =>
Expand Down Expand Up @@ -85,9 +91,45 @@ public static IAppBuilder AddMicrosoftIdentityWebApi(
updateOptions(options);
}

MicrosoftIdentityApplicationOptions applicationOptions = new();
configureOptions(applicationOptions);
ConfigureClaimsValidation(options, applicationOptions.AllowWebApiToBeAuthorizedByACL);

return app.UseOAuthBearerAuthentication(options);
}

internal static void ConfigureClaimsValidation(
OAuthBearerAuthenticationOptions options,
bool allowWebApiToBeAuthorizedByACL)
{
if (allowWebApiToBeAuthorizedByACL)
{
return;
}

IOAuthBearerAuthenticationProvider provider =
options.Provider ?? new OAuthBearerAuthenticationProvider();

options.Provider = new OAuthBearerAuthenticationProvider
{
OnRequestToken = provider.RequestToken,
OnApplyChallenge = provider.ApplyChallenge,
OnValidateIdentity = async context =>
{
if (!context.Ticket.Identity.Claims.Any(x => x.Type == ClaimConstants.Scope
|| x.Type == ClaimConstants.Scp
|| x.Type == ClaimConstants.Roles
|| x.Type == ClaimConstants.Role))
{
context.Rejected();
return;
}

await provider.ValidateIdentity(context).ConfigureAwait(false);
},
};
}

/// <summary>
/// Adds a protected web app.
/// </summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
using System.Runtime.CompilerServices;

// Allow this assembly to be serviced when run on desktop CLR
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Identity.Web.AgentIdentities\Microsoft.Identity.Web.AgentIdentities.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Identity.Web.DownstreamApi\Microsoft.Identity.Web.DownstreamApi.csproj" />
<ProjectReference Condition="'$(TargetFramework)' == 'net472'" Include="..\..\src\Microsoft.Identity.Web.OWIN\Microsoft.Identity.Web.OWIN.csproj" />
<ProjectReference Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))" Include="..\..\src\Microsoft.Identity.Web.MicrosoftGraph\Microsoft.Identity.Web.MicrosoftGraph.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Identity.Web\Microsoft.Identity.Web.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Identity.Web.Azure\Microsoft.Identity.Web.Azure.csproj" />
Expand Down Expand Up @@ -53,6 +54,7 @@
<Compile Include="CacheExtensionsTests.cs" />
<Compile Include="CacheEncryptionTests.cs" />
<Compile Include="TestDistributedCache.cs" />
<Compile Include="OwinClaimsValidationTests.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
78 changes: 78 additions & 0 deletions tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;

Check failure on line 7 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'Owin' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
using Microsoft.Owin.Security;

Check failure on line 8 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'Owin' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
using Microsoft.Owin.Security.OAuth;

Check failure on line 9 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'Owin' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
using Xunit;

namespace Microsoft.Identity.Web.Test
{
public class OwinClaimsValidationTests
{
[Fact]
public async Task MissingScopesAndRoles_AuthenticationFailsAsync()
{
OAuthBearerAuthenticationOptions options = CreateOptions();
AppBuilderExtension.ConfigureClaimsValidation(options, allowWebApiToBeAuthorizedByACL: false);
OAuthValidateIdentityContext context = CreateContext(options);

await options.Provider.ValidateIdentity(context);

Assert.False(context.IsValidated);
}

[Theory]
[InlineData(ClaimConstants.Scope)]
[InlineData(ClaimConstants.Scp)]
[InlineData(ClaimConstants.Roles)]
[InlineData(ClaimTypes.Role)]
public async Task ScopeOrRoleClaim_AuthenticationSucceedsAsync(string claimType)
{
OAuthBearerAuthenticationOptions options = CreateOptions();
AppBuilderExtension.ConfigureClaimsValidation(options, allowWebApiToBeAuthorizedByACL: false);
OAuthValidateIdentityContext context = CreateContext(options, claimType);

await options.Provider.ValidateIdentity(context);

Assert.True(context.IsValidated);
}

[Fact]
public async Task MissingScopesAndRoles_WithAclAuthorization_AuthenticationSucceedsAsync()
{
OAuthBearerAuthenticationOptions options = CreateOptions();
AppBuilderExtension.ConfigureClaimsValidation(options, allowWebApiToBeAuthorizedByACL: true);
OAuthValidateIdentityContext context = CreateContext(options);

await options.Provider.ValidateIdentity(context);

Assert.True(context.IsValidated);
}

private static OAuthBearerAuthenticationOptions CreateOptions()

Check failure on line 56 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'OAuthBearerAuthenticationOptions' could not be found (are you missing a using directive or an assembly reference?)
{
return new OAuthBearerAuthenticationOptions
{
Provider = new OAuthBearerAuthenticationProvider(),
};
}

private static OAuthValidateIdentityContext CreateContext(

Check failure on line 64 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'OAuthValidateIdentityContext' could not be found (are you missing a using directive or an assembly reference?)
OAuthBearerAuthenticationOptions options,

Check failure on line 65 in tests/Microsoft.Identity.Web.Test/OwinClaimsValidationTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The type or namespace name 'OAuthBearerAuthenticationOptions' could not be found (are you missing a using directive or an assembly reference?)
string? claimType = null)
{
ClaimsIdentity identity = claimType is null
? new CaseSensitiveClaimsIdentity(authenticationType: "Bearer")
: new CaseSensitiveClaimsIdentity(new[] { new Claim(claimType, "value") }, "Bearer");
AuthenticationTicket ticket = new(identity, new AuthenticationProperties());
OAuthValidateIdentityContext context = new(new OwinContext(), options, ticket);
context.Validated();
return context;
}

}
}
Loading