Skip to content

Commit

Permalink
update Id web to support authentication handlers other than JwtBearer… (
Browse files Browse the repository at this point in the history
#1498)

* update Id web to support authentication handlers other than JwtBearer and provide extension method

* make class static

* PR feedback from george
  • Loading branch information
jennyf19 authored Oct 23, 2021
1 parent 5e82c89 commit 491b8ef
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 28 deletions.
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 @@ -764,15 +771,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 @@ -818,6 +824,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);
}
}
}

0 comments on commit 491b8ef

Please sign in to comment.