Skip to content

Commit 3d797e3

Browse files
committed
feat: Refactor authentication and configuration options
1 parent d80f89b commit 3d797e3

File tree

8 files changed

+104
-87
lines changed

8 files changed

+104
-87
lines changed
Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
using System;
2-
using System.Threading.Tasks;
31
using Microsoft.AspNetCore.Authentication.Cookies;
42
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
53
using Microsoft.AspNetCore.Builder;
64
using Microsoft.AspNetCore.Http;
75
using Microsoft.Extensions.DependencyInjection;
8-
using Microsoft.Extensions.Options;
96

107
namespace LinkDotNet.Blog.Web.Authentication.OpenIdConnect;
118

129
public static class AuthExtensions
1310
{
1411
public static void UseAuthentication(this IServiceCollection services)
1512
{
16-
using var provider = services.BuildServiceProvider();
17-
var authInformation = provider.GetRequiredService<IOptions<AuthInformation>>();
18-
1913
services.Configure<CookiePolicyOptions>(options =>
2014
{
2115
options.CheckConsentNeeded = _ => false;
@@ -29,52 +23,11 @@ public static void UseAuthentication(this IServiceCollection services)
2923
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
3024
})
3125
.AddCookie()
32-
.AddOpenIdConnect(authInformation.Value.Provider, options =>
33-
{
34-
options.Authority = $"https://{authInformation.Value.Domain}";
35-
options.ClientId = authInformation.Value.ClientId;
36-
options.ClientSecret = authInformation.Value.ClientSecret;
37-
38-
options.ResponseType = "code";
39-
40-
options.Scope.Clear();
41-
options.Scope.Add("openid");
42-
options.Scope.Add("profile");
26+
.AddOpenIdConnect();
4327

44-
// Set the callback path, so Auth provider will call back to http://localhost:1234/callback
45-
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth provider dashboard
46-
options.CallbackPath = new PathString("/callback");
47-
48-
// Configure the Claims Issuer to be Auth provider
49-
options.ClaimsIssuer = authInformation.Value.Provider;
50-
51-
options.Events = new OpenIdConnectEvents
52-
{
53-
OnRedirectToIdentityProviderForSignOut = async context => await HandleRedirect(authInformation.Value, context),
54-
};
55-
});
28+
services.ConfigureOptions<ConfigureOpenIdConnectOptions>();
5629

5730
services.AddHttpContextAccessor();
5831
services.AddScoped<ILoginManager, AuthLoginManager>();
5932
}
60-
61-
private static Task HandleRedirect(AuthInformation auth, RedirectContext context)
62-
{
63-
var postLogoutUri = context.Properties.RedirectUri;
64-
if (!string.IsNullOrEmpty(postLogoutUri))
65-
{
66-
if (postLogoutUri.StartsWith('/'))
67-
{
68-
var request = context.Request;
69-
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
70-
}
71-
72-
auth.LogoutUri += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
73-
}
74-
75-
context.Response.Redirect(auth.LogoutUri);
76-
context.HandleResponse();
77-
78-
return Task.CompletedTask;
79-
}
8033
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Options;
6+
7+
namespace LinkDotNet.Blog.Web.Authentication.OpenIdConnect;
8+
9+
public sealed class ConfigureOpenIdConnectOptions : IConfigureNamedOptions<OpenIdConnectOptions>
10+
{
11+
private readonly AuthInformation authInformation;
12+
13+
public ConfigureOpenIdConnectOptions(IOptions<AuthInformation> authInformation)
14+
{
15+
ArgumentNullException.ThrowIfNull(authInformation);
16+
this.authInformation = authInformation.Value;
17+
}
18+
19+
public void Configure(string? name, OpenIdConnectOptions options)
20+
{
21+
if (name != authInformation.Provider)
22+
{
23+
return;
24+
}
25+
26+
Configure(options);
27+
}
28+
29+
public void Configure(OpenIdConnectOptions options)
30+
{
31+
ArgumentNullException.ThrowIfNull(options);
32+
options.Authority = $"https://{authInformation.Domain}";
33+
options.ClientId = authInformation.ClientId;
34+
options.ClientSecret = authInformation.ClientSecret;
35+
36+
options.ResponseType = "code";
37+
38+
options.Scope.Clear();
39+
options.Scope.Add("openid");
40+
options.Scope.Add("profile");
41+
42+
// Set the callback path, so Auth provider will call back to http://localhost:1234/callback
43+
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth provider dashboard
44+
options.CallbackPath = new PathString("/callback");
45+
46+
// Configure the Claims Issuer to be Auth provider
47+
options.ClaimsIssuer = authInformation.Provider;
48+
49+
options.Events = new OpenIdConnectEvents
50+
{
51+
OnRedirectToIdentityProviderForSignOut = context => HandleRedirect(context),
52+
};
53+
}
54+
55+
private Task HandleRedirect(RedirectContext context)
56+
{
57+
var logoutUri = authInformation.LogoutUri;
58+
var postLogoutUri = context.Properties.RedirectUri;
59+
60+
if (!string.IsNullOrEmpty(postLogoutUri))
61+
{
62+
if (postLogoutUri.StartsWith('/'))
63+
{
64+
var request = context.Request;
65+
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
66+
}
67+
68+
logoutUri += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
69+
}
70+
71+
context.Response.Redirect(logoutUri);
72+
context.HandleResponse();
73+
74+
return Task.CompletedTask;
75+
}
76+
}

src/LinkDotNet.Blog.Web/ConfigurationExtension.cs

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ private static IServiceCollection AddApplicationConfiguration(this IServiceColle
4444
settings.IsGiscusEnabled = giscusSection.Exists();
4545

4646
config.Bind(settings);
47-
});
47+
})
48+
.ValidateDataAnnotations()
49+
.ValidateOnStart();
4850
return services;
4951
}
5052

@@ -53,10 +55,7 @@ private static IServiceCollection AddAuthenticationConfigurations(this IServiceC
5355
ArgumentNullException.ThrowIfNull(services);
5456

5557
services.AddOptions<AuthInformation>()
56-
.Configure<IConfiguration>((settings, config) =>
57-
{
58-
config.GetSection(AuthInformation.AuthInformationSection).Bind(settings);
59-
});
58+
.BindConfiguration(AuthInformation.AuthInformationSection);
6059
return services;
6160
}
6261

@@ -65,10 +64,7 @@ private static IServiceCollection AddIntroductionConfigurations(this IServiceCol
6564
ArgumentNullException.ThrowIfNull(services);
6665

6766
services.AddOptions<Introduction>()
68-
.Configure<IConfiguration>((settings, config) =>
69-
{
70-
config.GetSection(Introduction.IntroductionSection).Bind(settings);
71-
});
67+
.BindConfiguration(Introduction.IntroductionSection);
7268
return services;
7369
}
7470

@@ -77,10 +73,7 @@ private static IServiceCollection AddSocialConfigurations(this IServiceCollectio
7773
ArgumentNullException.ThrowIfNull(services);
7874

7975
services.AddOptions<Social>()
80-
.Configure<IConfiguration>((settings, config) =>
81-
{
82-
config.GetSection(Social.SocialSection).Bind(settings);
83-
});
76+
.BindConfiguration(Social.SocialSection);
8477
return services;
8578
}
8679

@@ -89,10 +82,7 @@ private static IServiceCollection AddProfileInformationConfigurations(this IServ
8982
ArgumentNullException.ThrowIfNull(services);
9083

9184
services.AddOptions<ProfileInformation>()
92-
.Configure<IConfiguration>((settings, config) =>
93-
{
94-
config.GetSection(ProfileInformation.ProfileInformationSection).Bind(settings);
95-
});
85+
.BindConfiguration(ProfileInformation.ProfileInformationSection);
9686
return services;
9787
}
9888

@@ -101,10 +91,7 @@ private static IServiceCollection AddGiscusConfiguration(this IServiceCollection
10191
ArgumentNullException.ThrowIfNull(services);
10292

10393
services.AddOptions<GiscusConfiguration>()
104-
.Configure<IConfiguration>((settings, config) =>
105-
{
106-
config.GetSection(GiscusConfiguration.GiscusConfigurationSection).Bind(settings);
107-
});
94+
.BindConfiguration(GiscusConfiguration.GiscusConfigurationSection);
10895
return services;
10996
}
11097

@@ -113,10 +100,7 @@ private static IServiceCollection AddDisqusConfiguration(this IServiceCollection
113100
ArgumentNullException.ThrowIfNull(services);
114101

115102
services.AddOptions<DisqusConfiguration>()
116-
.Configure<IConfiguration>((settings, config) =>
117-
{
118-
config.GetSection(DisqusConfiguration.DisqusConfigurationSection).Bind(settings);
119-
});
103+
.BindConfiguration(DisqusConfiguration.DisqusConfigurationSection);
120104
return services;
121105
}
122106

@@ -125,10 +109,7 @@ private static IServiceCollection AddSupportMeConfiguration(this IServiceCollect
125109
ArgumentNullException.ThrowIfNull(services);
126110

127111
services.AddOptions<SupportMeConfiguration>()
128-
.Configure<IConfiguration>((settings, config) =>
129-
{
130-
config.GetSection(SupportMeConfiguration.SupportMeConfigurationSection).Bind(settings);
131-
});
112+
.BindConfiguration(SupportMeConfiguration.SupportMeConfigurationSection);
132113
return services;
133114
}
134115

@@ -137,11 +118,7 @@ private static IServiceCollection AddImageUploadConfiguration(this IServiceColle
137118
ArgumentNullException.ThrowIfNull(services);
138119

139120
services.AddOptions<UploadConfiguration>()
140-
.Configure<IConfiguration>((settings, config) =>
141-
{
142-
config.GetSection(UploadConfiguration.ConfigurationSection)
143-
.Bind(settings);
144-
});
121+
.BindConfiguration(UploadConfiguration.ConfigurationSection);
145122
return services;
146123
}
147124
}

src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ public sealed partial class BlogPostPublisher : IJob
1515
private readonly ILogger<BlogPostPublisher> logger;
1616
private readonly IRepository<BlogPost> repository;
1717
private readonly ICacheInvalidator cacheInvalidator;
18+
private readonly TimeProvider timeProvider;
1819

19-
public BlogPostPublisher(IRepository<BlogPost> repository, ICacheInvalidator cacheInvalidator, ILogger<BlogPostPublisher> logger)
20+
public BlogPostPublisher(
21+
IRepository<BlogPost> repository,
22+
ICacheInvalidator cacheInvalidator,
23+
TimeProvider timeProvider,
24+
ILogger<BlogPostPublisher> logger)
2025
{
2126
this.repository = repository;
2227
this.cacheInvalidator = cacheInvalidator;
28+
this.timeProvider = timeProvider;
2329
this.logger = logger;
2430
}
2531

@@ -55,7 +61,7 @@ private async Task<int> PublishScheduledBlogPostsAsync()
5561

5662
private async Task<IPagedList<BlogPost>> GetScheduledBlogPostsAsync()
5763
{
58-
var now = DateTime.UtcNow;
64+
var now = timeProvider.GetUtcNow().DateTime;
5965
var scheduledBlogPosts = await repository.GetAllAsync(
6066
filter: b => b.ScheduledPublishDate != null && b.ScheduledPublishDate <= now);
6167

src/LinkDotNet.Blog.Web/Features/Services/UserRecordService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ public sealed partial class UserRecordService : IUserRecordService
1313
private readonly IRepository<UserRecord> userRecordRepository;
1414
private readonly NavigationManager navigationManager;
1515
private readonly AuthenticationStateProvider authenticationStateProvider;
16+
private readonly TimeProvider timeProvider;
1617
private readonly ILogger<UserRecordService> logger;
1718

1819
public UserRecordService(
1920
IRepository<UserRecord> userRecordRepository,
2021
NavigationManager navigationManager,
2122
AuthenticationStateProvider authenticationStateProvider,
23+
TimeProvider timeProvider,
2224
ILogger<UserRecordService> logger)
2325
{
2426
this.userRecordRepository = userRecordRepository;
2527
this.navigationManager = navigationManager;
2628
this.authenticationStateProvider = authenticationStateProvider;
29+
this.timeProvider = timeProvider;
2730
this.logger = logger;
2831
}
2932

@@ -51,7 +54,7 @@ private async ValueTask GetAndStoreUserRecordAsync()
5154

5255
var record = new UserRecord
5356
{
54-
DateClicked = DateOnly.FromDateTime(DateTime.UtcNow),
57+
DateClicked = DateOnly.FromDateTime(timeProvider.GetUtcNow().DateTime),
5558
UrlClicked = url,
5659
};
5760

src/LinkDotNet.Blog.Web/ServiceExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static class ServiceExtensions
1717
{
1818
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
1919
{
20+
services.AddSingleton(TimeProvider.System);
2021
services.AddScoped<ILocalStorageService, LocalStorageService>();
2122
services.AddScoped<ISortOrderCalculator, SortOrderCalculator>();
2223
services.AddScoped<IUserRecordService, UserRecordService>();

tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public BlogPostPublisherTests()
1919
{
2020
cacheInvalidator = Substitute.For<ICacheInvalidator>();
2121

22-
sut = new BlogPostPublisher(Repository, cacheInvalidator, Substitute.For<ILogger<BlogPostPublisher>>());
22+
sut = new BlogPostPublisher(Repository, cacheInvalidator, TimeProvider.System, Substitute.For<ILogger<BlogPostPublisher>>());
2323
}
2424

2525
[Fact]

tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/UserRecordServiceTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public UserRecordServiceTests()
2323
repositoryMock,
2424
fakeNavigationManager,
2525
fakeAuthenticationStateProvider,
26+
TimeProvider.System,
2627
Substitute.For<ILogger<UserRecordService>>());
2728
}
2829

0 commit comments

Comments
 (0)