Completed API and redirect login test

feat: Update OAuth client handling and permissions, enhance authentication claims, and adjust environment settings
This commit is contained in:
warrenchen 2026-04-23 13:56:33 +09:00
parent f9a66dccad
commit 09589ef631
10 changed files with 119 additions and 5 deletions

View File

@ -1,6 +1,8 @@
ASPNETCORE_ENVIRONMENT=Development
ConnectionStrings__Default=Host=localhost;Database=member_center;Username=postgres;Password=postgres
Auth__Issuer=http://localhost:7850/
Auth__WebLoginUrl=http://localhost:5080/account/login
Auth__AllowedLoginReturnUrlPrefixes=http://localhost:7850/
Auth__MemberCenterAudience=member_center_api
Auth__SendEngineAudience=send_engine_api
SendEngine__BaseUrl=http://localhost:6060

View File

@ -162,6 +162,11 @@ public class AdminOAuthClientsController : ControllerBase
descriptor.DisplayName = request.Name;
descriptor.ClientType = request.ClientType;
if (string.Equals(request.ClientType, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
descriptor.ClientSecret = null;
}
await ApplyPermissionsAsync(descriptor, request.Usage);
descriptor.RedirectUris.Clear();
foreach (var uri in redirectUris)

View File

@ -56,6 +56,7 @@ public class OAuthController : ControllerBase
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
principal.SetClaim(OpenIddictConstants.Claims.Subject, user.Id.ToString());
if (!string.IsNullOrWhiteSpace(user.SecurityStamp))
{
principal.SetClaim(SecurityStampClaimType, user.SecurityStamp);

View File

@ -58,6 +58,7 @@ public class TokenController : ControllerBase
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
principal.SetClaim(OpenIddictConstants.Claims.Subject, user.Id.ToString());
if (!string.IsNullOrWhiteSpace(user.SecurityStamp))
{
principal.SetClaim(SecurityStampClaimType, user.SecurityStamp);

View File

@ -231,6 +231,12 @@ app.Use(async (context, next) =>
{
if (context.User.Identity?.IsAuthenticated == true)
{
if (context.User.HasClaim(claim => claim.Type == "client_usage"))
{
await next();
return;
}
var subject = context.User.FindFirstValue(OpenIddictConstants.Claims.Subject)
?? context.User.FindFirstValue(ClaimTypes.NameIdentifier);

View File

@ -13,7 +13,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5050",
"applicationUrl": "http://localhost:7850",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -79,7 +79,12 @@ public sealed class AuthResourceRegistryService : IAuthResourceRegistryService
OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Profile,
"profile:basic.read"
"profile:basic.read",
"profile:basic.write",
"profile:addresses.read",
"profile:addresses.write",
"profile:subscriptions.read",
"profile:subscriptions.write"
], cancellationToken);
await EnsureUsagePermissionsAsync("webhook_outbound", [

View File

@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Npgsql;
using OpenIddict.Abstractions;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.CommandLine;
@ -283,10 +284,60 @@ migrateCommand.SetHandler(async (string? connectionString, string? appsettings,
Console.WriteLine("Migrations completed.");
}, connectionStringOption, appsettingsOption, targetMigrationOption);
var syncOAuthClientsCommand = new Command("sync-oauth-clients", "Refresh OAuth client permissions from usage defaults");
syncOAuthClientsCommand.AddOption(connectionStringOption);
syncOAuthClientsCommand.AddOption(appsettingsOption);
syncOAuthClientsCommand.SetHandler(async (string? connectionString, string? appsettings) =>
{
var resolvedConnection = ResolveConnectionString(connectionString, appsettings, noPrompt: true);
if (string.IsNullOrWhiteSpace(resolvedConnection))
{
Console.Error.WriteLine("Connection string is required.");
return;
}
var services = BuildServices(resolvedConnection);
await using var scope = services.CreateAsyncScope();
var registry = scope.ServiceProvider.GetRequiredService<IAuthResourceRegistryService>();
await registry.EnsureDefaultsAsync();
var applicationManager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
var applications = new List<object>();
await foreach (var application in applicationManager.ListAsync())
{
applications.Add(application);
}
var updated = 0;
foreach (var application in applications)
{
var properties = await applicationManager.GetPropertiesAsync(application);
if (!properties.TryGetValue("usage", out var usageElement))
{
continue;
}
var usage = usageElement.GetString();
if (string.IsNullOrWhiteSpace(usage))
{
continue;
}
var descriptor = new OpenIddictApplicationDescriptor();
await applicationManager.PopulateAsync(descriptor, application);
await ApplyOAuthClientPermissionsAsync(descriptor, registry, usage);
await applicationManager.UpdateAsync(application, descriptor);
updated++;
}
Console.WriteLine($"OAuth clients synchronized: {updated}.");
}, connectionStringOption, appsettingsOption);
root.AddCommand(initCommand);
root.AddCommand(addAdminCommand);
root.AddCommand(resetCommand);
root.AddCommand(migrateCommand);
root.AddCommand(syncOAuthClientsCommand);
return await root.InvokeAsync(args);
@ -303,6 +354,13 @@ static IServiceProvider BuildServices(string connectionString)
options.UseOpenIddict();
});
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<MemberCenterDbContext>();
});
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.User.RequireUniqueEmail = true;
@ -320,6 +378,37 @@ static IServiceProvider BuildServices(string connectionString)
return services.BuildServiceProvider();
}
static async Task ApplyOAuthClientPermissionsAsync(
OpenIddictApplicationDescriptor descriptor,
IAuthResourceRegistryService registry,
string usage)
{
descriptor.Permissions.Clear();
descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
if (UsesAuthorizationCodeFlow(usage))
{
descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
descriptor.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode);
descriptor.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken);
descriptor.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code);
}
else
{
descriptor.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials);
}
var scopes = await registry.GetAllowedScopesForUsageAsync(usage);
foreach (var scope in scopes)
{
descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
}
}
static bool UsesAuthorizationCodeFlow(string usage) =>
string.Equals(usage, "web_login", StringComparison.OrdinalIgnoreCase)
|| string.Equals(usage, "webhook_outbound", StringComparison.OrdinalIgnoreCase);
static string? ResolveConnectionString(string? connectionString, string? appsettingsPath, bool noPrompt)
{
var targetPath = ResolveAppsettingsPath(appsettingsPath);

View File

@ -1,11 +1,11 @@
{
"MemberCenter": {
"ApiBaseUrl": "http://localhost:7850",
"WebLoginClientId": "",
"WebLoginClientId": "f48329ef38c54a62b627585a75c9b5d5",
"WebLoginRedirectPath": "/auth/callback",
"WebLoginScopes": "openid email profile profile:basic.read profile:basic.write profile:addresses.read profile:addresses.write profile:subscriptions.read profile:subscriptions.write",
"ServiceClientId": "",
"ServiceClientSecret": "",
"ServiceClientId": "e9fe7ae413c54ae49432eca6474648d3",
"ServiceClientSecret": "To/WaVObQgxCaZwzGexfp/pvUwI2G5o1r55v4sRukYw=",
"ServiceScopes": "profile:basic.read profile:addresses.read"
},
"Logging": {

View File

@ -204,6 +204,11 @@ public class OAuthClientsController : Controller
descriptor.DisplayName = model.Name;
descriptor.ClientType = model.ClientType;
if (string.Equals(model.ClientType, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
descriptor.ClientSecret = null;
}
await ApplyPermissionsAsync(descriptor, model.Usage);
descriptor.RedirectUris.Clear();
foreach (var uri in redirectUris)