Skip to content

Commit

Permalink
Add verification of issuer signing key with integration test (#2356)
Browse files Browse the repository at this point in the history
* Fix and test for the signing key issuer validator
* Enable mock metadata in samples (new SimulateOidc web API under IntegrationTests)
* Updating the UI test to use ExternalApp.Start
  • Loading branch information
jmprieur authored Jul 30, 2023
1 parent 9d595bb commit a8bbcc4
Show file tree
Hide file tree
Showing 23 changed files with 693 additions and 70 deletions.
7 changes: 7 additions & 0 deletions Microsoft.Identity.Web.sln
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Grap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphServiceClientTests", "tests\IntegrationTests\GraphServiceClientTests\GraphServiceClientTests.csproj", "{F686A507-CAC6-4349-9112-27F5AEFBF12B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimulateOidc", "tests\IntegrationTests\SimulateOidc\SimulateOidc.csproj", "{9014A1C1-7552-4950-AB86-7BE97B301701}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -391,6 +393,10 @@ Global
{F686A507-CAC6-4349-9112-27F5AEFBF12B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F686A507-CAC6-4349-9112-27F5AEFBF12B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F686A507-CAC6-4349-9112-27F5AEFBF12B}.Release|Any CPU.Build.0 = Release|Any CPU
{9014A1C1-7552-4950-AB86-7BE97B301701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9014A1C1-7552-4950-AB86-7BE97B301701}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9014A1C1-7552-4950-AB86-7BE97B301701}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9014A1C1-7552-4950-AB86-7BE97B301701}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -469,6 +475,7 @@ Global
{4DF02DF7-D092-4F45-8892-8A1D3E612706} = {1DDE1AAC-5AE6-4725-94B6-A26C58D3423F}
{608F0E0B-A52D-4E0F-9B1A-BA9BDA866484} = {1DDE1AAC-5AE6-4725-94B6-A26C58D3423F}
{F686A507-CAC6-4349-9112-27F5AEFBF12B} = {A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}
{9014A1C1-7552-4950-AB86-7BE97B301701} = {A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {104367F1-CE75-4F40-B32F-F14853973187}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Globalization;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -213,8 +214,6 @@ private static void AddMicrosoftIdentityWebApiImplementation(
microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate;
}

mergedOptions.TokenValidationParameters.EnableAadSigningKeyIssuerValidation();

// If you provide a token decryption certificate, it will be used to decrypt the token
// TODO use the credential loader
if (mergedOptions.TokenDecryptionCredentials != null)
Expand All @@ -230,6 +229,13 @@ private static void AddMicrosoftIdentityWebApiImplementation(
options.Events = new JwtBearerEvents();
}

options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation();
options.Events.OnMessageReceived = async context =>
{
context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager;
await Task.CompletedTask.ConfigureAwait(false);
};

// When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can
// be used from the controllers.

Expand Down
38 changes: 19 additions & 19 deletions tests/DevApps/WebAppCallsWebApiCallsGraph/Client/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "msidentitysamplestesting.onmicrosoft.com",
"TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
"ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",
"EnablePiiLogging": true,
"EnableCacheSynchronization": false,
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "msidentitysamplestesting.onmicrosoft.com",
"TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
"ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",
"EnablePiiLogging": true,
"EnableCacheSynchronization": false,

// To call an API
"ClientSecret": "secret"
//"ClientCertificates": [
// {
// "SourceType": "",
// "Container": "",
// "ReferenceOrValue": ""
// }
//]
},
// To call an API
"ClientSecret": "secret"
//"ClientCertificates": [
// {
// "SourceType": "",
// "Container": "",
// "ReferenceOrValue": ""
// }
//]
},
"TodoList": {
// TodoListScope is the scope of the Web API you want to call.
"Scopes": [ "api://a4c2469b-cf84-4145-8f5f-cb7bacf814bc/access_as_user" ],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task<IEnumerable<Todo>> GetAsync()
var result = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" }).ConfigureAwait(false); // for testing OBO

var result2 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read.all" },
tokenAcquisitionOptions: new TokenAcquisitionOptions { ForceRefresh = true }).ConfigureAwait(false); // for testing OBO
tokenAcquisitionOptions: new TokenAcquisitionOptions { ForceRefresh = true }).ConfigureAwait(false); // for testing OBO

await RegisterPeriodicCallbackForLongProcessing(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,9 @@
<ProjectReference Include="..\..\..\..\src\Microsoft.Identity.Web.MicrosoftGraph\Microsoft.Identity.Web.MicrosoftGraph.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Identity.Web\Microsoft.Identity.Web.csproj" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.0.0-preview" />
<PackageReference Include="Microsoft.IdentityModel.Protocols" Version="7.0.0-preview" />
<PackageReference Include="StackExchange.Redis" Version="2.2.4" />
<!--CVE-2021-24112-->
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="7.0.0-preview" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.*" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.*" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
"Instance": "https://login.microsoftonline.com/",
"Domain": "msidentitysamplestesting.onmicrosoft.com",
"TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",

// Or instead of Instance + TenantId, you can use the Authority
// "Authority": "https://login.microsoftonline.com/7f58f645-c190-4ce5-9de4-e2b7acd2a6ab/",

// To exercise the signing-key issuer:
// - uncomment the following line (Authority)
// - start the 3 projects:
// - SimulateOidc
// - WebAppsCallsWebApiCallGraph\TodoListService
// - WebAppsCallsWebApiCallGraph\\TodoListClient,
// - Navigate to the todo list. this provokes a 401.
// "Authority": "https://localhost:1234/v2.0",
"ClientId": "a4c2469b-cf84-4145-8f5f-cb7bacf814bc", //"712ae8d7-548a-4306-95b6-ee9117ee86f0", JWE clientID
"ClientSecret": "secret",
"Scopes": "access_as_user",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
<ProjectReference Include="..\..\Microsoft.Identity.Web.Test.Common\Microsoft.Identity.Web.Test.Common.csproj" />
<ProjectReference Include="..\..\Microsoft.Identity.Web.Test.LabInfrastructure\Microsoft.Identity.Web.Test.LabInfrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:62162",
"sslPort": 44306
"applicationUrl": "http://localhost:5000",
"sslPort": 5001
}
},
"profiles": {
Expand Down
16 changes: 5 additions & 11 deletions tests/IntegrationTests/IntegrationTestService/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Globalization;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -15,6 +9,7 @@
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Test.Common;
using Microsoft.Identity.Web.Test.LabInfrastructure;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;

namespace IntegrationTestService
{
Expand Down Expand Up @@ -49,6 +44,9 @@ public void ConfigureServices(IServiceCollection services)
Configuration.GetSection(TestConstants.SectionNameCalledApi))
.AddMicrosoftGraph(Configuration.GetSection("GraphBeta"));

// Will be overriden by tests if needed
services.AddInMemoryTokenCaches();

services.Configure<MicrosoftIdentityOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.ClientSecret = secret;
Expand All @@ -59,13 +57,9 @@ public void ConfigureServices(IServiceCollection services)
options.ClientSecret = secret;
});

// services.AddAuthorization();
services.AddAuthorization();

services.AddRazorPages();
//services.AddRazorPages(options =>
//{
// options.Conventions.AuthorizePage("/SecurePage");
//});
services.AddControllers();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Default": "Debug",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace SimulateOidc.Controllers
{
// We are using a controller rather than static pages here to serve the OIDC metadata
// so that we can dynamically replace the jwks_uri with the correct URL for this service.
[Route("v2.0/.well-known")]
[ApiController]
[AllowAnonymous]
public class MetadataController : ControllerBase
{
[HttpGet("/v2.0/.well-known/openid-configuration")]
public IActionResult OpenIdConnectConfiguration()
{
// Get the openIdConnectConfiguration from the embedded resource
string openIdConnectDocumentString = System.Text.Encoding.UTF8.GetString(Properties.Resource.openid_configuration);

// Replace the jwks URI based on the base URL of this service.
string hostUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/";
string openIdConnectDocumentJsonString = openIdConnectDocumentString.Replace("https://localhost/", hostUrl, System.StringComparison.InvariantCulture);

// The openIdConnectConfiguration is served as a JSON string
return new ContentResult
{
ContentType = "application/json",
Content = openIdConnectDocumentJsonString,
StatusCode = 200
};
}

[HttpGet("/v2.0/.well-known/keys.json")]
public IActionResult Keys()
{
byte[] keysDocument = Properties.Resource.keys;
return new FileContentResult(keysDocument, "application/json");
}
}
}
32 changes: 32 additions & 0 deletions tests/IntegrationTests/SimulateOidc/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace SimulateOidc
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

app.Run();
}
}
}

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

Loading

0 comments on commit a8bbcc4

Please sign in to comment.