Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update Id web to support authentication handlers other than JwtBearer… #1498

Merged
merged 3 commits into from
Oct 23, 2021
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
10 changes: 5 additions & 5 deletions src/Microsoft.Identity.Web/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;

namespace Microsoft.Identity.Web
{
Expand All @@ -14,7 +14,7 @@ internal static class HttpContextExtensions
/// <param name="httpContext">HTTP context.</param>
/// <param name="token">Token to preserve after the token is validated so that
/// it can be used in the actions.</param>
internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, JwtSecurityToken? token)
internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, SecurityToken? token)
{
// lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
lock (httpContext)
Expand All @@ -27,13 +27,13 @@ internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, Jw
/// Get the parsed information about the token used to call the web API.
/// </summary>
/// <param name="httpContext">HTTP context associated with the current request.</param>
/// <returns><see cref="JwtSecurityToken"/> used to call the web API.</returns>
internal static JwtSecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext)
/// <returns><see cref="SecurityToken"/> used to call the web API.</returns>
internal static SecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext)
{
// lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
lock (httpContext)
{
return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken;
return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as SecurityToken;
}
}
}
Expand Down
21 changes: 19 additions & 2 deletions src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml

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

41 changes: 34 additions & 7 deletions src/Microsoft.Identity.Web/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
using Microsoft.Extensions.Primitives;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.TokenCacheProviders;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;

namespace Microsoft.Identity.Web
Expand Down Expand Up @@ -251,6 +252,12 @@ public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
authenticationScheme = GetEffectiveAuthenticationScheme(authenticationScheme);
MergedOptions mergedOptions = GetOptions(authenticationScheme);

if (string.IsNullOrEmpty(mergedOptions.Instance))
{
var mergedOptionsMonitor = _serviceProvider.GetRequiredService<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
mergedOptionsMonitor.Get(authenticationScheme);
}

user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false);

var application = GetOrBuildConfidentialClientApplication(mergedOptions);
Expand Down Expand Up @@ -759,15 +766,14 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
try
{
// In web API, validatedToken will not be null
JwtSecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI();
SecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI();

// In the case the token is a JWE (encrypted token), we use the decrypted token.
string? tokenUsedToCallTheWebApi = GetActualToken(validatedToken);

// Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API
if (validatedToken != null)
if (tokenUsedToCallTheWebApi != null)
{
// In the case the token is a JWE (encrypted token), we use the decrypted token.
string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData
: validatedToken.InnerToken.RawData;

var builder = application
.AcquireTokenOnBehalfOf(
scopes.Except(_scopesRequestedByMsal),
Expand Down Expand Up @@ -809,6 +815,27 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
}
}

private static string? GetActualToken(SecurityToken? validatedToken)
{
JwtSecurityToken? jwtSecurityToken = validatedToken as JwtSecurityToken;
if (jwtSecurityToken != null)
{
// In the case the token is a JWE (encrypted token), we use the decrypted token.
return jwtSecurityToken.InnerToken == null ? jwtSecurityToken.RawData
: jwtSecurityToken.InnerToken.RawData;
}

JsonWebToken? jsonWebToken = validatedToken as JsonWebToken;
if (jsonWebToken != null)
{
// In the case the token is a JWE (encrypted token), we use the decrypted token.
return jsonWebToken.InnerToken == null ? jsonWebToken.EncodedToken
: jsonWebToken.InnerToken.EncodedToken;
}

return null;
}

/// <summary>
/// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.Internal;

namespace Microsoft.Identity.Web
{
Expand Down Expand Up @@ -73,7 +74,8 @@ public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisiti
CallsWebApiImplementation(
Services,
JwtBearerAuthenticationScheme,
configureConfidentialClientApplicationOptions);
configureConfidentialClientApplicationOptions,
ConfigurationSection);

return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder(
Services,
Expand All @@ -83,26 +85,22 @@ public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisiti
internal static void CallsWebApiImplementation(
IServiceCollection services,
string jwtBearerAuthenticationScheme,
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions)
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
IConfigurationSection? configurationSection = null)
{
services.Configure(jwtBearerAuthenticationScheme, configureConfidentialClientApplicationOptions);

services.AddTokenAcquisition();
WebApiBuilders.EnableTokenAcquisition(
configureConfidentialClientApplicationOptions,
jwtBearerAuthenticationScheme,
services,
configurationSection);

services.AddHttpContextAccessor();

services.AddOptions<JwtBearerOptions>(jwtBearerAuthenticationScheme)
.Configure<IServiceProvider, IOptionsMonitor<MergedOptions>, IOptionsMonitor<ConfidentialClientApplicationOptions>, IOptions<ConfidentialClientApplicationOptions>>((
options,
serviceProvider,
mergedOptionsMonitor,
ccaOptionsMonitor,
ccaOptions) =>
.Configure((options) =>
{
MergedOptions mergedOptions = mergedOptionsMonitor.Get(jwtBearerAuthenticationScheme);

MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptions.Value, mergedOptions); // legacy scenario w/out auth scheme
MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptionsMonitor.Get(jwtBearerAuthenticationScheme), mergedOptions); // w/auth scheme

options.Events ??= new JwtBearerEvents();

var onTokenValidatedHandler = options.Events.OnTokenValidated;
Expand Down
50 changes: 50 additions & 0 deletions src/Microsoft.Identity.Web/WebApiExtensions/WebApiBuilders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Client;

namespace Microsoft.Identity.Web.Internal
{
/// <summary>
/// Web API authentication builder.
/// </summary>
public static class WebApiBuilders
{
/// <summary>
/// Allows a higher level abstraction of security token (i.e. System.IdentityModel.Tokens.Jwt and more modern, Microsoft.IdentityModel.JsonWebTokens)
/// to be used with Microsoft Identity Web.
/// Developers should continue to use `EnableTokenAcquisitionToCallDownstreamApi`.
/// This API is not considered part of the public API and may change.
/// </summary>
/// <param name="configureConfidentialClientApplicationOptions">The action to configure <see cref="ConfidentialClientApplicationOptions"/>.</param>
/// <param name="authenticationScheme">Authentication scheme.</param>
/// <param name="services">The services being configured.</param>
/// <param name="configuration">Configuration.</param>
/// <returns>The authentication builder to chain.</returns>
public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisition(
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
string authenticationScheme,
IServiceCollection services,
IConfiguration? configuration)
{
services.AddOptions<ConfidentialClientApplicationOptions>(authenticationScheme)
.Configure<IOptionsMonitor<MergedOptions>>((
ccaOptions, mergedOptionsMonitor) =>
{
configureConfidentialClientApplicationOptions(ccaOptions);
MergedOptions mergedOptions = mergedOptionsMonitor.Get(authenticationScheme);
MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptions, mergedOptions);
});

services.AddTokenAcquisition();

return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder(
services,
configuration as IConfigurationSection);
}
}
}