Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OidcFic;

namespace Microsoft.Extensions.DependencyInjection
Expand All @@ -20,6 +21,7 @@ public static class OidcFicSignedAssertionProviderExtensions
/// <returns>the service collection for chaining.</returns>
public static IServiceCollection AddOidcFic(this IServiceCollection services)
{
services.AddTokenAcquisition(true);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OidcFic can't work with scoped token acquisition :-(

services.TryAddEnumerable(ServiceDescriptor.Singleton<ICustomSignedAssertionProvider, OidcIdpSignedAssertionLoader>());
return services;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web.Sidecar.Models;

Expand All @@ -19,6 +21,7 @@ public static void AddDownstreamApiRequestEndpoints(this WebApplication app)
ProducesProblem(401);
}

[AllowAnonymous]
private static async Task<Results<Ok<AuthorizationHeaderResult>, ProblemHttpResult>> AuthorizationHeaderAsync(
HttpContext httpContext,
[FromRoute] string apiName,
Expand All @@ -27,9 +30,9 @@ private static async Task<Results<Ok<AuthorizationHeaderResult>, ProblemHttpResu
[FromQuery] string? tenant,
[FromBody] DownstreamApiOptions? optionsOverride,
[FromServices] IAuthorizationHeaderProvider headerProvider,
[FromServices] IConfiguration configuration)
[FromServices] IOptionsMonitor<DownstreamApiOptions> optionsMonitor)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's better to get the options directly. and we'll be ready if we want to have a CalldownstreamApis endpoint

{
DownstreamApiOptions? options = configuration.GetSection($"DownstreamApi:{apiName}").Get<DownstreamApiOptions>();
DownstreamApiOptions? options = optionsMonitor.Get(apiName);

if (options is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ private static Results<Ok<ValidateAuthorizationHeaderResult>, ProblemHttpResult>
{
httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
}
var claimsPrincipal = httpContext.User;
var token = claimsPrincipal.GetBootstrapToken() as JsonWebToken;

var token = httpContext.GetTokenUsedToCallWebAPI() as JsonWebToken;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See also the simplication in program.cs


if (token is null)
{
Expand Down
26 changes: 11 additions & 15 deletions src/Microsoft.Identity.Web.Sidecar/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Sidecar.Endpoints;
Expand All @@ -21,26 +22,21 @@ public static void Main(string[] args)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();

// Add the agent identities and downstream APIs
builder.Services.AddAgentIdentities()
.AddDownstreamApis(builder.Configuration.GetSection("DownstreamApis"));

builder.Services.AddHealthChecks();

// Disable claims mapping.
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't want Wilson to transform the claims.

JsonWebTokenHandler.DefaultMapInboundClaims = false;
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.Events ??= new();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Simplication here as this is already done.

options.Events.OnTokenValidated = context =>
{
Debug.Assert(context.SecurityToken is JsonWebToken, "Token should always be JsonWebToken");
var token = (JsonWebToken)context.SecurityToken;

if (context.Principal?.Identities.FirstOrDefault() is null)
{
context.Fail("No principal or no identity");
return Task.FromResult(context);
}

context.Principal.Identities.First().BootstrapContext = token.InnerToken is not null ? token.InnerToken : token;
return Task.FromResult(context);
};
// Enable the right role claim type.
options.TokenValidationParameters.RoleClaimType = "roles";
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

important for AuthZ

options.TokenValidationParameters.NameClaimType = "sub";
});

builder.Services.AddAuthorization();
Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.Identity.Web.Sidecar/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ For more info see https://aka.ms/dotnet-template-ms-identity-platform
],

"EnablePiiLogging": false,
"AllowWebApiToBeAuthorizedByACL": true,
"AllowWebApiToBeAuthorizedByACL": true
},

"DownstreamApi": {
"DownstreamApis": {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the usual convention

"me": {
"BaseUrl": "https://graph.microsoft.com/v1.0/",
"RelativePath": "me",
Expand All @@ -38,7 +38,7 @@ For more info see https://aka.ms/dotnet-template-ms-identity-platform
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AllowedHosts": "*"
}


Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
[assembly: InternalsVisibleTo("TokenAcquirerTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("GenerateMergeOptionsMethods, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.SideCar, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Net.Http.Headers;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
Expand All @@ -27,15 +28,26 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
});
builder.ConfigureServices(services =>
{
// Given we add the Json file after the initial configuration, and that
// downstream APIs are added to a IOptions, we need to re-add the downstream APIs
// with the new config
IConfiguration? configuration = services!
.First(s => s.ServiceType == typeof(IConfiguration))
?.ImplementationFactory
?.Invoke(null!) as IConfiguration;

services!.AddDownstreamApis(configuration!.GetSection("DownstreamApis"));
});
}
}

public class ValidateEndpointTests : IClassFixture<SidecarApiFactory>
public class EndpointsE2ETests : IClassFixture<SidecarApiFactory>
{
private readonly SidecarApiFactory _factory;

public ValidateEndpointTests(SidecarApiFactory factory) => _factory = factory;
public EndpointsE2ETests(SidecarApiFactory factory) => _factory = factory;
string agentIdentity = "d84da24a-2ea2-42b8-b5ab-8637ec208024"; // Replace with the actual agent identity
string userUpn = "[email protected]"; // Replace with the actual user upn.

[Fact]
public async Task Validate_WhenBadTokenAsync()
Expand Down Expand Up @@ -65,6 +77,38 @@ public async Task Validate_WhenGoodTokenAsync()
Assert.NotEmpty(content);
}

[Fact]
public async Task GetAuthorizationHeaderForAgentUserIdentityAuthenticated()
{
string agentIdentity = "d84da24a-2ea2-42b8-b5ab-8637ec208024"; // Replace with the actual agent identity
string userUpn = "[email protected]"; // Replace with the actual user upn.

// Getting a token to call the API.
string authorizationHeader = await GetAuthorizationHeaderToCallTheSideCarAsync();

// Calling the API
var client = _factory.CreateClient();

client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authorizationHeader);
var response = await client.PostAsync($"/AuthorizationHeader/MsGraph?agentidentity={agentIdentity}&agentUsername={userUpn}", null);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);

}

[Fact]
public async Task GetAuthorizationHeaderForAgentUserIdentityUnauthenticated()
{
// Calling the API
var client = _factory.CreateClient();

var response = await client.PostAsync($"/AuthorizationHeader/MsGraph?agentidentity={agentIdentity}&agentUsername={userUpn}", null);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);

}


private static async Task<string> GetAuthorizationHeaderToCallTheSideCarAsync()
{
ServiceCollection services = new();
Expand Down
6 changes: 5 additions & 1 deletion tests/E2E Tests/Sidecar.Tests/appsettings.agentids.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
},

"DownstreamApis": {

"MsGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"RelativePath": "/me",
"Scopes": [ "User.Read" ]
}
}
}