ASP.NET MVC 5 SSO登录问题:首次访问需手动登录订阅
Got it, let's work through this SSO requirement you have. You've got a solid working SSO setup across your ASP.NET MVC 5 apps using Identity and Forms Authentication, but you need to block that automatic login for first-time visitors to an app—they have to explicitly click to log in, subscribe to the app, and behave exactly like an unauthenticated user until they do so. Since you already have shared machine keys configured, that's half the battle. Here's how to implement this:
By default, Forms Authentication will automatically pick up the shared auth cookie and authenticate the user across apps. We need to intercept this process in each app to check if the user has an active subscription for that specific app.
Add this logic to your Global.asax.cs in each application:
protected void Application_AuthenticateRequest(object sender, EventArgs e) { // Only run if the user is globally authenticated but might not have app access if (User.Identity.IsAuthenticated) { // Get the current user's ID (using Identity's extension method) var userId = User.Identity.GetUserId(); // Replace CurrentAppId with a unique identifier for this application (e.g., a GUID or string like "AppInventory") var hasSubscription = db.UserSubscriptions.Any(u => u.UserId == userId && u.AppId == CurrentAppId); if (!hasSubscription) { // Sign the user out LOCALLY to reset their auth state for this app FormsAuthentication.SignOut(); // Redirect to the app-specific login/subscribe page Response.Redirect("/Account/SubscribeLogin", false); Context.ApplicationInstance.CompleteRequest(); } } }
Create a dedicated login page (e.g., SubscribeLogin.cshtml) that forces users to explicitly log in (even if they're authenticated elsewhere) and complete a subscription for the app.
Here's a sample action in your AccountController:
[HttpGet] [AllowAnonymous] public ActionResult SubscribeLogin(string returnUrl) { // If the user somehow landed here but already has a subscription, send them to the intended page if (User.Identity.IsAuthenticated) { var userId = User.Identity.GetUserId(); if (db.UserSubscriptions.Any(u => u.UserId == userId && u.AppId == CurrentAppId)) { return RedirectToLocal(returnUrl); } } ViewBag.ReturnUrl = returnUrl; return View(); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> SubscribeLogin(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } // Validate credentials using your existing Identity logic var user = await UserManager.FindAsync(model.Email, model.Password); if (user == null) { ModelState.AddModelError("", "Invalid email or password."); return View(model); } // Create a subscription record for this user and app db.UserSubscriptions.Add(new UserSubscription { UserId = user.Id, AppId = CurrentAppId, CreatedDate = DateTime.UtcNow }); await db.SaveChangesAsync(); // Now sign the user in normally—this will create the auth cookie that works with your SSO setup await SignInManager.SignInAsync(user, model.RememberMe, false); return RedirectToLocal(returnUrl); }
Add a custom action filter to ensure users can't bypass the subscription check by navigating directly to protected pages. This acts as a safety net alongside the Application_AuthenticateRequest logic.
public class RequiresSubscriptionAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var httpContext = filterContext.HttpContext; if (httpContext.User.Identity.IsAuthenticated) { var userId = httpContext.User.Identity.GetUserId(); var hasSubscription = db.UserSubscriptions.Any(u => u.UserId == userId && u.AppId == CurrentAppId); if (!hasSubscription) { // Redirect to the subscribe login page filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "controller", "Account" }, { "action", "SubscribeLogin" }, { "returnUrl", httpContext.Request.Url.PathAndQuery } }); } } base.OnActionExecuting(filterContext); } }
Register this filter globally in Global.asax.cs to apply it to all actions:
protected void Application_Start() { // ... other startup logic GlobalFilters.Filters.Add(new RequiresSubscriptionAttribute()); }
- Subscription Revocation: If a user's subscription to an app is removed, the above logic will automatically redirect them back to the
SubscribeLoginpage on their next visit. - Global Logout: Ensure your logout action signs out the user globally (using
FormsAuthentication.SignOut()andAuthenticationManager.SignOut()for Identity) so they're logged out of all apps. - Consistent Auth Cookie Settings: Double-check that all apps share the same
formsconfiguration inweb.config(samename,path,domainif applicable) to maintain SSO functionality once the subscription is active.
- First visit: Access App B while logged into App A → you should be redirected to App B's
SubscribeLoginpage, with no automatic login. - After subscribing: Log in via the
SubscribeLoginpage, complete the subscription → you'll be logged into App B, and future visits will use SSO as normal. - Post-revocation: If the subscription is deleted, next visit to App B will again redirect to
SubscribeLogin.
内容的提问来源于stack exchange,提问作者Sepak

