Skip to content

Commit 491b8ef

Browse files
authored
update Id web to support authentication handlers other than JwtBearer… (#1498)
* update Id web to support authentication handlers other than JwtBearer and provide extension method * make class static * PR feedback from george
1 parent 5e82c89 commit 491b8ef

5 files changed

Lines changed: 120 additions & 28 deletions

File tree

src/Microsoft.Identity.Web/HttpContextExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
using System.IdentityModel.Tokens.Jwt;
54
using Microsoft.AspNetCore.Http;
5+
using Microsoft.IdentityModel.Tokens;
66

77
namespace Microsoft.Identity.Web
88
{
@@ -14,7 +14,7 @@ internal static class HttpContextExtensions
1414
/// <param name="httpContext">HTTP context.</param>
1515
/// <param name="token">Token to preserve after the token is validated so that
1616
/// it can be used in the actions.</param>
17-
internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, JwtSecurityToken? token)
17+
internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, SecurityToken? token)
1818
{
1919
// lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
2020
lock (httpContext)
@@ -27,13 +27,13 @@ internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, Jw
2727
/// Get the parsed information about the token used to call the web API.
2828
/// </summary>
2929
/// <param name="httpContext">HTTP context associated with the current request.</param>
30-
/// <returns><see cref="JwtSecurityToken"/> used to call the web API.</returns>
31-
internal static JwtSecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext)
30+
/// <returns><see cref="SecurityToken"/> used to call the web API.</returns>
31+
internal static SecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext)
3232
{
3333
// lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
3434
lock (httpContext)
3535
{
36-
return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken;
36+
return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as SecurityToken;
3737
}
3838
}
3939
}

src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml

Lines changed: 19 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Identity.Web/TokenAcquisition.cs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
using Microsoft.Extensions.Primitives;
2626
using Microsoft.Identity.Client;
2727
using Microsoft.Identity.Web.TokenCacheProviders;
28-
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
28+
using Microsoft.IdentityModel.JsonWebTokens;
29+
using Microsoft.IdentityModel.Tokens;
2930
using Microsoft.Net.Http.Headers;
3031

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

255+
if (string.IsNullOrEmpty(mergedOptions.Instance))
256+
{
257+
var mergedOptionsMonitor = _serviceProvider.GetRequiredService<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
258+
mergedOptionsMonitor.Get(authenticationScheme);
259+
}
260+
254261
user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false);
255262

256263
var application = GetOrBuildConfidentialClientApplication(mergedOptions);
@@ -764,15 +771,14 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
764771
try
765772
{
766773
// In web API, validatedToken will not be null
767-
JwtSecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI();
774+
SecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI();
775+
776+
// In the case the token is a JWE (encrypted token), we use the decrypted token.
777+
string? tokenUsedToCallTheWebApi = GetActualToken(validatedToken);
768778

769779
// Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API
770-
if (validatedToken != null)
780+
if (tokenUsedToCallTheWebApi != null)
771781
{
772-
// In the case the token is a JWE (encrypted token), we use the decrypted token.
773-
string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData
774-
: validatedToken.InnerToken.RawData;
775-
776782
var builder = application
777783
.AcquireTokenOnBehalfOf(
778784
scopes.Except(_scopesRequestedByMsal),
@@ -818,6 +824,27 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
818824
}
819825
}
820826

827+
private static string? GetActualToken(SecurityToken? validatedToken)
828+
{
829+
JwtSecurityToken? jwtSecurityToken = validatedToken as JwtSecurityToken;
830+
if (jwtSecurityToken != null)
831+
{
832+
// In the case the token is a JWE (encrypted token), we use the decrypted token.
833+
return jwtSecurityToken.InnerToken == null ? jwtSecurityToken.RawData
834+
: jwtSecurityToken.InnerToken.RawData;
835+
}
836+
837+
JsonWebToken? jsonWebToken = validatedToken as JsonWebToken;
838+
if (jsonWebToken != null)
839+
{
840+
// In the case the token is a JWE (encrypted token), we use the decrypted token.
841+
return jsonWebToken.InnerToken == null ? jsonWebToken.EncodedToken
842+
: jsonWebToken.InnerToken.EncodedToken;
843+
}
844+
845+
return null;
846+
}
847+
821848
/// <summary>
822849
/// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal.
823850
/// </summary>

src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilder.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Extensions.Options;
1010
using Microsoft.Identity.Client;
11+
using Microsoft.Identity.Web.Internal;
1112

1213
namespace Microsoft.Identity.Web
1314
{
@@ -73,7 +74,8 @@ public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisiti
7374
CallsWebApiImplementation(
7475
Services,
7576
JwtBearerAuthenticationScheme,
76-
configureConfidentialClientApplicationOptions);
77+
configureConfidentialClientApplicationOptions,
78+
ConfigurationSection);
7779

7880
return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder(
7981
Services,
@@ -83,26 +85,22 @@ public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisiti
8385
internal static void CallsWebApiImplementation(
8486
IServiceCollection services,
8587
string jwtBearerAuthenticationScheme,
86-
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions)
88+
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
89+
IConfigurationSection? configurationSection = null)
8790
{
8891
services.Configure(jwtBearerAuthenticationScheme, configureConfidentialClientApplicationOptions);
8992

90-
services.AddTokenAcquisition();
93+
WebApiBuilders.EnableTokenAcquisition(
94+
configureConfidentialClientApplicationOptions,
95+
jwtBearerAuthenticationScheme,
96+
services,
97+
configurationSection);
98+
9199
services.AddHttpContextAccessor();
92100

93101
services.AddOptions<JwtBearerOptions>(jwtBearerAuthenticationScheme)
94-
.Configure<IServiceProvider, IOptionsMonitor<MergedOptions>, IOptionsMonitor<ConfidentialClientApplicationOptions>, IOptions<ConfidentialClientApplicationOptions>>((
95-
options,
96-
serviceProvider,
97-
mergedOptionsMonitor,
98-
ccaOptionsMonitor,
99-
ccaOptions) =>
102+
.Configure((options) =>
100103
{
101-
MergedOptions mergedOptions = mergedOptionsMonitor.Get(jwtBearerAuthenticationScheme);
102-
103-
MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptions.Value, mergedOptions); // legacy scenario w/out auth scheme
104-
MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptionsMonitor.Get(jwtBearerAuthenticationScheme), mergedOptions); // w/auth scheme
105-
106104
options.Events ??= new JwtBearerEvents();
107105

108106
var onTokenValidatedHandler = options.Events.OnTokenValidated;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Options;
8+
using Microsoft.Identity.Client;
9+
10+
namespace Microsoft.Identity.Web.Internal
11+
{
12+
/// <summary>
13+
/// Web API authentication builder.
14+
/// </summary>
15+
public static class WebApiBuilders
16+
{
17+
/// <summary>
18+
/// Allows a higher level abstraction of security token (i.e. System.IdentityModel.Tokens.Jwt and more modern, Microsoft.IdentityModel.JsonWebTokens)
19+
/// to be used with Microsoft Identity Web.
20+
/// Developers should continue to use `EnableTokenAcquisitionToCallDownstreamApi`.
21+
/// This API is not considered part of the public API and may change.
22+
/// </summary>
23+
/// <param name="configureConfidentialClientApplicationOptions">The action to configure <see cref="ConfidentialClientApplicationOptions"/>.</param>
24+
/// <param name="authenticationScheme">Authentication scheme.</param>
25+
/// <param name="services">The services being configured.</param>
26+
/// <param name="configuration">Configuration.</param>
27+
/// <returns>The authentication builder to chain.</returns>
28+
public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisition(
29+
Action<ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
30+
string authenticationScheme,
31+
IServiceCollection services,
32+
IConfiguration? configuration)
33+
{
34+
services.AddOptions<ConfidentialClientApplicationOptions>(authenticationScheme)
35+
.Configure<IOptionsMonitor<MergedOptions>>((
36+
ccaOptions, mergedOptionsMonitor) =>
37+
{
38+
configureConfidentialClientApplicationOptions(ccaOptions);
39+
MergedOptions mergedOptions = mergedOptionsMonitor.Get(authenticationScheme);
40+
MergedOptions.UpdateMergedOptionsFromConfidentialClientApplicationOptions(ccaOptions, mergedOptions);
41+
});
42+
43+
services.AddTokenAcquisition();
44+
45+
return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder(
46+
services,
47+
configuration as IConfigurationSection);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)