Skip to content

Commit dea3490

Browse files
committed
Added HttpClient user authentication using OpenID connect.
1 parent 9559a29 commit dea3490

32 files changed

+1053
-84
lines changed

Http/samples/Web/Controllers/HomeController.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,15 @@ public async Task<IActionResult> CallApiAsClient()
4646

4747
return View("CallApi");
4848
}
49+
50+
[Authorize]
51+
public async Task<IActionResult> CallApiAsUser()
52+
{
53+
HttpClient client = _httpClientFactory.CreateClient("api-user-client");
54+
55+
string response = await client.GetStringAsync("test");
56+
ViewBag.Json = JsonNode.Parse(response)!.ToString();
57+
58+
return View("CallApi");
59+
}
4960
}

Http/samples/Web/Program.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
o =>
7070
{
7171
o.Scope = "api";
72-
}));
72+
}))
73+
.AddOpenIdConnect();
7374

7475
// add HTTP client with OAuth client authentication
7576
builder.Services
@@ -81,6 +82,16 @@
8182
})
8283
.AddOAuthClientAuthentication();
8384

85+
// add HTTP client with OAuth user authentication
86+
builder.Services
87+
.AddHttpClient(
88+
"api-user-client",
89+
client =>
90+
{
91+
client.BaseAddress = new Uri("https://demo.duendesoftware.com/api/");
92+
})
93+
.AddOpenIdConnectAuthentication();
94+
8495
WebApplication app = builder.Build();
8596

8697
// Configure the HTTP request pipeline.
@@ -96,6 +107,7 @@
96107

97108
app.UseRouting();
98109

110+
app.UseAuthentication();
99111
app.UseAuthorization();
100112

101113
app.MapControllerRoute(

Http/samples/Web/appsettings.Development.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"Logging": {
33
"LogLevel": {
4-
"Default": "Information",
4+
"Default": "Debug",
55
"Microsoft.AspNetCore": "Warning"
66
}
77
}

Http/src/AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect/DependencyInjection/OAuthHttpClientAuthenticationBuilderExtensions.cs

Lines changed: 0 additions & 47 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Licensed under the MIT License.
2+
// Copyright (c) 2018-2022 the AppCore .NET project.
3+
4+
using System;
5+
using AppCore.Diagnostics;
6+
using AppCore.Extensions.Http.Authentication.OAuth;
7+
using AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
10+
// ReSharper disable once CheckNamespace
11+
namespace Microsoft.Extensions.DependencyInjection;
12+
13+
/// <summary>
14+
/// Provides extension methods to register OAuth authentication.
15+
/// </summary>
16+
public static class OpenIdConnectHttpClientAuthenticationBuilderExtensions
17+
{
18+
/// <summary>
19+
/// Adds OAuth client credentials authentication scheme by inferring the configuration from a OpenID connect
20+
/// authentication scheme.
21+
/// </summary>
22+
/// <param name="builder">The <see cref="IOAuthClientFromAuthenticationSchemeBuilder"/>.</param>
23+
/// <param name="configure"></param>
24+
/// <returns></returns>
25+
public static void OpenIdConnect(
26+
this IOAuthClientFromAuthenticationSchemeBuilder builder,
27+
Action<OpenIdConnectClientOptions>? configure = null)
28+
{
29+
Ensure.Arg.NotNull(builder);
30+
31+
IServiceCollection services = builder.Services;
32+
33+
services.AddHttpClientAuthentication()
34+
.AddScheme<
35+
OpenIdConnectClientOptions,
36+
OAuthParameters,
37+
OAuthClientHandler>(builder.Scheme, configure);
38+
39+
services.TryAddEnumerable(
40+
new[]
41+
{
42+
ServiceDescriptor
43+
.Transient<IOAuthOptionsResolver,
44+
OpenIdConnectClientOptionsResolver>(),
45+
});
46+
}
47+
48+
/// <summary>
49+
/// Adds OAuth user authentication by using authentication tokens from a ASP.NET Core
50+
/// OpenID connect authentication scheme.
51+
/// </summary>
52+
/// <param name="builder">The <see cref="IHttpClientAuthenticationBuilder"/>.</param>
53+
/// <param name="scheme">The name of the client authentication scheme.</param>
54+
/// <param name="configure"></param>
55+
/// <returns></returns>
56+
public static IHttpClientAuthenticationBuilder AddOpenIdConnect(
57+
this IHttpClientAuthenticationBuilder builder,
58+
string scheme,
59+
Action<OpenIdConnectUserOptions>? configure = null)
60+
{
61+
Ensure.Arg.NotNull(builder);
62+
63+
IServiceCollection services = builder.Services;
64+
65+
services.AddHttpContextAccessor();
66+
67+
services.AddHttpClientAuthentication()
68+
.AddScheme<
69+
OpenIdConnectUserOptions,
70+
OpenIdConnectUserParameters,
71+
OpenIdConnectUserHandler>(scheme, configure);
72+
73+
services.TryAddEnumerable(
74+
new[]
75+
{
76+
ServiceDescriptor.Transient<IOAuthOptionsResolver, OpenIdConnectUserOptionsResolver>(),
77+
});
78+
79+
services.TryAdd(new[]
80+
{
81+
ServiceDescriptor.Scoped<OpenIdConnectUserTokenService, OpenIdConnectUserTokenService>(),
82+
ServiceDescriptor.Scoped<OpenIdConnectUserTokenStore, OpenIdConnectUserTokenStore>()
83+
});
84+
85+
return builder;
86+
}
87+
88+
/// <summary>
89+
/// Adds OAuth user authentication by using authentication tokens from a ASP.NET Core
90+
/// OpenID connect authentication scheme.
91+
/// </summary>
92+
/// <param name="builder">The <see cref="IHttpClientAuthenticationBuilder"/>.</param>
93+
/// <param name="configure"></param>
94+
/// <returns></returns>
95+
public static IHttpClientAuthenticationBuilder AddOpenIdConnect(
96+
this IHttpClientAuthenticationBuilder builder,
97+
Action<OpenIdConnectUserOptions>? configure = null)
98+
{
99+
return AddOpenIdConnect(builder, OpenIdConnectUserDefaults.AuthenticationScheme, configure);
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed under the MIT License.
2+
// Copyright (c) 2018-2022 the AppCore .NET project.
3+
4+
using AppCore.Diagnostics;
5+
using AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
6+
7+
// ReSharper disable once CheckNamespace
8+
namespace Microsoft.Extensions.DependencyInjection;
9+
10+
/// <summary>
11+
/// Provides extension methods to add OAuth authentication to a HttpClient.
12+
/// </summary>
13+
public static class OpenIdConnectHttpClientBuilderExtensions
14+
{
15+
/// <summary>
16+
/// Adds OpenID connect authentications.
17+
/// </summary>
18+
/// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
19+
/// <param name="scheme">The name of the client authentication scheme.</param>
20+
/// <param name="parameters"></param>
21+
/// <returns></returns>
22+
public static IHttpClientBuilder AddOpenIdConnectAuthentication(
23+
this IHttpClientBuilder builder,
24+
string scheme,
25+
OpenIdConnectUserParameters? parameters = null)
26+
{
27+
Ensure.Arg.NotNull(builder);
28+
return builder.AddAuthentication<OpenIdConnectUserParameters, OpenIdConnectUserHandler>(scheme, parameters);
29+
}
30+
31+
/// <summary>
32+
/// Adds OpenID connect authentications with the default scheme.
33+
/// </summary>
34+
/// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
35+
/// <param name="parameters"></param>
36+
/// <returns></returns>
37+
public static IHttpClientBuilder AddOpenIdConnectAuthentication(
38+
this IHttpClientBuilder builder,
39+
OpenIdConnectUserParameters? parameters = null)
40+
{
41+
return builder.AddOpenIdConnectAuthentication(OpenIdConnectUserDefaults.AuthenticationScheme, parameters);
42+
}
43+
}

Http/src/AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect/OpenIdConnectOAuthClientOptions.cs renamed to Http/src/AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect/OpenIdConnectClientOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
88
/// <summary>
99
/// Provides the options how to derive <see cref="OAuthClientOptions"/> from <see cref="OpenIdConnectOptions"/>.
1010
/// </summary>
11-
public class OpenIdConnectOAuthClientOptions : AuthenticationSchemeOAuthClientOptions
11+
public class OpenIdConnectClientOptions : AuthenticationSchemeOAuthClientOptions
1212
{
1313
/// <summary>
1414
/// Scope values as space separated list to use when client configuration is inferred from OpenID Connect scheme.

Http/src/AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect/OpenIdConnectOAuthClientOptionsResolver.cs renamed to Http/src/AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect/OpenIdConnectClientOptionsResolver.cs

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,37 @@
11
// Licensed under the MIT License.
22
// Copyright (c) 2018-2022 the AppCore .NET project.
33

4-
using System;
54
using System.Threading.Tasks;
65
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
76
using Microsoft.Extensions.Options;
8-
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
97

108
namespace AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
119

12-
internal sealed class OpenIdConnectOAuthClientOptionsResolver
10+
internal sealed class OpenIdConnectClientOptionsResolver
1311
: AuthenticationSchemeOAuthClientOptionsResolver<
14-
OpenIdConnectOAuthClientOptions,
12+
OpenIdConnectClientOptions,
1513
OpenIdConnectOptions,
1614
OpenIdConnectHandler
1715
>
1816
{
19-
public OpenIdConnectOAuthClientOptionsResolver(
17+
public OpenIdConnectClientOptionsResolver(
2018
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider authenticationSchemeProvider,
21-
IOptionsMonitor<OpenIdConnectOAuthClientOptions> clientOptions,
19+
IOptionsMonitor<OpenIdConnectClientOptions> clientOptions,
2220
IOptionsMonitor<OpenIdConnectOptions> authenticationSchemeOptions)
2321
: base(authenticationSchemeProvider, authenticationSchemeOptions, clientOptions)
2422
{
2523
}
2624

25+
protected override string? GetSchemeName(OpenIdConnectClientOptions options)
26+
{
27+
return options.Scheme;
28+
}
29+
2730
protected override async Task<OAuthClientOptions> GetOptionsFromSchemeAsync(
28-
OpenIdConnectOAuthClientOptions clientOptions,
31+
OpenIdConnectClientOptions clientOptions,
2932
OpenIdConnectOptions oidcOptions)
3033
{
31-
OpenIdConnectConfiguration oidcConfig;
32-
try
33-
{
34-
oidcConfig = await oidcOptions.ConfigurationManager!.GetConfigurationAsync(default)
35-
.ConfigureAwait(false);
36-
}
37-
catch (Exception e)
38-
{
39-
throw new InvalidOperationException(
40-
$"Unable to load OpenID configuration for configured scheme: {e.Message}");
41-
}
42-
43-
var result = new OAuthClientOptions
44-
{
45-
TokenEndpoint = new Uri(oidcConfig.TokenEndpoint),
46-
ClientId = oidcOptions.ClientId,
47-
ClientSecret = oidcOptions.ClientSecret
48-
};
34+
OAuthClientOptions result = await oidcOptions.GetOAuthClientOptionsAsync(default);
4935

5036
if (!string.IsNullOrWhiteSpace(clientOptions.Scope))
5137
{
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using IdentityModel;
5+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
6+
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
7+
8+
namespace AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
9+
10+
internal static class OpenIdConnectOptionsExtensions
11+
{
12+
public static async Task<OAuthClientOptions> GetOAuthClientOptionsAsync(
13+
this OpenIdConnectOptions options,
14+
CancellationToken cancellationToken = default)
15+
{
16+
OpenIdConnectConfiguration oidcConfig;
17+
try
18+
{
19+
oidcConfig = await options.ConfigurationManager!.GetConfigurationAsync(cancellationToken)
20+
.ConfigureAwait(false);
21+
}
22+
catch (Exception e)
23+
{
24+
throw new InvalidOperationException(
25+
$"Unable to load OpenID configuration for configured scheme: {e.Message}");
26+
}
27+
28+
string? tokenRevocationEndpoint = oidcConfig.AdditionalData.TryGetValue(
29+
OidcConstants.Discovery.RevocationEndpoint,
30+
out object? value)
31+
? value?.ToString()
32+
: null;
33+
34+
var result = new OAuthClientOptions
35+
{
36+
TokenEndpoint = new Uri(oidcConfig.TokenEndpoint),
37+
TokenRevocationEndpoint = tokenRevocationEndpoint != null ? new Uri(tokenRevocationEndpoint) : null,
38+
ClientId = options.ClientId,
39+
ClientSecret = options.ClientSecret
40+
};
41+
42+
return result;
43+
}
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace AppCore.Extensions.Http.Authentication.OAuth.AspNetCore.OpenIdConnect;
2+
3+
/// <summary>
4+
/// Provides default values for the OpenID Connect authentication.
5+
/// </summary>
6+
public class OpenIdConnectUserDefaults
7+
{
8+
/// <summary>
9+
/// The default value used for OpenID Connect scheme name.
10+
/// </summary>
11+
public const string AuthenticationScheme = "OpenIdConnect";
12+
}

0 commit comments

Comments
 (0)