ASP.NET Core中能否通过转发认证至另一方案拦截新用户创建?
Great question! Your core approach to avoiding premature user creation and messy identity merges later is totally on the right track. Let's walk through exactly how to implement this flow in ASP.NET Core 2.0/2.1 while keeping your requirements front and center.
1. Configure Multiple Authentication Schemes
First, register both your custom LTI/OAuth1.0 scheme and the trusted OpenID Connect providers (Google, Microsoft) in Startup.cs. Set one OpenID scheme as the default challenge fallback for new identities.
services.AddAuthentication(options => { options.DefaultChallengeScheme = "Google"; // Swap with "Microsoft" if preferred }) // Custom LTI/OAuth1.0 scheme configuration .AddOAuth("LTI", options => { // Fill in your LTI-specific settings: ClientId, ClientSecret, TokenEndpoint, etc. options.ClientId = "your-lti-client-id"; options.ClientSecret = "your-lti-client-secret"; options.CallbackPath = "/lti/signin"; options.Events = new OAuthEvents { OnCreatingTicket = async context => { // Extract key identity claims from the LTI provider response var ltiUserId = context.User.GetValue("user_id").ToString(); var ltiClaims = new List<Claim> { new Claim("lti_user_id", ltiUserId), // Add other relevant LTI claims (e.g., email, role) here }; // Check if this LTI identity is already linked to an existing user var existingUser = await _userManager.FindByLoginAsync("LTI", ltiUserId); if (existingUser == null) { // Store LTI claims temporarily (use encrypted cookie/session for security) var tempAuthProps = new AuthenticationProperties { RedirectUri = "/lti/post-openid-callback", Items = { { "temp_lti_claims", JsonConvert.SerializeObject(ltiClaims) } } }; // Trigger the OpenID challenge to verify the user's identity await context.HttpContext.ChallengeAsync(options.DefaultChallengeScheme, tempAuthProps); context.HandleResponse(); // Halt the LTI flow until OpenID completes } else { // Existing user: attach LTI claims to their authentication ticket context.Identity.AddClaims(ltiClaims); } } }; }) // Google OpenID scheme configuration .AddGoogle("Google", options => { options.ClientId = "your-google-client-id"; options.ClientSecret = "your-google-client-secret"; options.Events = new OpenIdConnectEvents { OnTokenValidated = async context => { // Retrieve the stored LTI claims from temporary authentication properties if (context.Properties.Items.TryGetValue("temp_lti_claims", out string ltiClaimsJson)) { var ltiClaims = JsonConvert.DeserializeObject<List<Claim>>(ltiClaimsJson); var googleEmail = context.Principal.FindFirstValue(ClaimTypes.Email); // Check if a user already exists with this Google email var existingUser = await _userManager.FindByEmailAsync(googleEmail); if (existingUser != null) { // Link the LTI identity to the existing user account await _userManager.AddLoginAsync( existingUser, new UserLoginInfo("LTI", ltiClaims.First(c => c.Type == "lti_user_id").Value, "LTI") ); } else { // Create a new user and link both the Google and LTI identities var newUser = new ApplicationUser { UserName = googleEmail, Email = googleEmail }; var createResult = await _userManager.CreateAsync(newUser); if (createResult.Succeeded) { await _userManager.AddLoginAsync(newUser, new UserLoginInfo("Google", context.Principal.FindFirstValue(ClaimTypes.NameIdentifier), "Google")); await _userManager.AddLoginAsync(newUser, new UserLoginInfo("LTI", ltiClaims.First(c => c.Type == "lti_user_id").Value, "LTI")); } } // Clean up temporary LTI claims context.Properties.Items.Remove("temp_lti_claims"); } } }; }) // Microsoft OpenID scheme configuration (mirror Google's logic) .AddMicrosoftAccount("Microsoft", options => { options.ClientId = "your-microsoft-client-id"; options.ClientSecret = "your-microsoft-client-secret"; // Add identical OnTokenValidated event logic as Google here }) // Ensure cookie authentication is enabled for persistent sessions .AddCookie();
2. Create a Callback to Resume LTI Flow
Add a dedicated endpoint to pick up the flow after the user completes OpenID verification:
[Route("/lti/post-openid-callback")] public async Task<IActionResult> PostOpenIdCallback() { // Resume and complete the LTI authentication flow var authResult = await HttpContext.AuthenticateAsync("LTI"); if (authResult.Succeeded) { // Sign the user in with their combined identity await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authResult.Principal); return RedirectToAction("Index", "Home"); } return BadRequest("Failed to complete LTI authentication after OpenID verification"); }
3. Critical Implementation Notes
- Secure Temporary Storage: ASP.NET Core encrypts cookies by default, but confirm your cookie middleware configuration to ensure temporary LTI claims are protected.
- Error Handling: Add logic to handle cases where the user denies OpenID consent (e.g., redirect to an error page or allow retrying the verification).
- Identity Matching: Use a reliable identifier like email to link accounts—avoid matching on usernames alone to prevent accidental merges.
- ASP.NET Core 2.x Quirks: In 2.0/2.1, explicitly call
AddCookie()if you're not using the default MVC template, as it's not added automatically.
Is Your Initial Approach Valid?
Absolutely! Your core idea of delaying user creation until a trusted secondary verification is exactly the right way to avoid messy identity merges down the line. The only adjustments needed are around persisting temporary claims and resuming the original LTI flow after OpenID authentication.
内容的提问来源于stack exchange,提问作者hemp

