locked
Route to a specific action explicitly from middleware RRS feed

  • Question

  • User373134933 posted

    I'm still trying to migrate my transparent login router to ASP.NET Core 3 with the new endpoint routing. My current approach is to insert a middleware before the routing happens. When this middleware detects that the user is not logged in, the login action must be invoked instead of whatever the URL requested. When the user has logged in, the same original URL will then serve the requested page. Here's my middleware in the Startup.Configure method:

    ...
    app.UseRouting();
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.Use(async (context, next) =>
    {
    	if (!context.User.Claims.Any())
    	{
    		var routeData = context.GetRouteData();
    		routeData.Values["controller"] = "Account";
    		routeData.Values["action"] = "Login";
    	}
    	// Call the next delegate/middleware in the pipeline
    	await next();
    });
    
    app.UseEndpoints(endpoints =>
    ...

    It correctly determines the logged in state but then can't change the routing. The shown code will serve the Account/Login view, but the code still executes the Home/Index action. This doesn't match so I probably destroyed the routing data with this.

    How can I force the Account/Login action and view to be called here from the middleware, without changing the URL (no redirect allowed)?

    Sunday, May 31, 2020 2:20 PM

All replies

  • User-2054057000 posted

    It correctly determines the logged in state but then can't change the routing.

    You can use:

    context.Response.Redirect("/controllername");

    You can learn more about EndPoint Routing from this article.

    Sunday, May 31, 2020 2:25 PM
  • User373134933 posted

    Thanks for your answer. But I said "without changing the URL (no redirect allowed)". Taking a look at your article, after already having read others and the official documentation.

    Update: I've read the article and it doesn't tell much more than your post, in my case. I've tried it and it changes the URL in the browser as expected. The problem is, after login, the original URL will be lost and the user is stuck on the login page instead of seeing what they originally wanted to see. That's why the URL must not be changed in the process. Basically, under the original request, the internal routing needs to be modified so that it does something else this time (i.e. not call the normal routed action but the login action instead, until the user is logged in).

    Sunday, May 31, 2020 2:28 PM
  • User-474980206 posted

    Normally when you redirect to the login page you pass the return url as a query string parameter. This allows the login page to redirect back to the original page after a successful login.

    While you could do a url rewrite, as the login page needs at least one post back (more on  incorrect data) to process the login, you would lose the url here anyway.

    Sunday, May 31, 2020 3:36 PM
  • User2078676645 posted

    Hi,

    After my inspection, RouteData is not jumping the route correctly, you can replace this part of the code with this.

    app.Use(async (context,next)=> {
                    //Console.WriteLine(context.Request.RouteValues["action"]);
                    context.Response.Cookies.Append("firstroute", "/" +
                        context.Request.RouteValues["controller"] + "/" +
                        context.Request.RouteValues["action"].ToString());
                    if (!context.User.Claims.Any())
                    {
                        context.Request.RouteValues["controller"] = "home";
                        context.Request.RouteValues["action"] = "login";
                    }
                    await next();
                 });

    You can use this method 'context.Request.RouteValues["action"]' to record paths and jump routes.

    And then, it will navigate to login, use [Httppost] to validate forms and get cookie values and navigate accordingly.

    	[HttpPost]
            public string login(string name,string passw)
            {
                string val="";
                if (Request.Cookies.ContainsKey("firstroute"))
                {
                     val = Request.Cookies["firstroute"];
                }
                Response.Redirect(val);
                return val;
            }

    Best Regards,

    Evern

    Monday, June 1, 2020 3:06 AM
  • User373134933 posted

    After playing a little more with it, I found a different approach that works as expected. I'm not changing the routes at all, I don't care about them as they can't be changed anyway. Instead, I use an internal URL rewrite for all unauthenticated requests. This is from the Configure method:

    ...
    app.UseStaticFiles();
    app.UseAuthentication();
    
    // Show the login form for all unauthenticated requests, without redirecting to another URL
    app.UseRewriter(new RewriteOptions()
    	.Add(rewriteContext =>
    	{
    		// Requires UseAuthentication called before
    		if (!rewriteContext.HttpContext.User.Claims.Any())
    		{
    			rewriteContext.Result = RuleResult.SkipRemainingRules;
    			rewriteContext.HttpContext.Items["OriginalRequestPath"] = rewriteContext.HttpContext.Request.Path;
    			rewriteContext.HttpContext.Request.Path = "/account/login";
    		}
    	}));
    app.UseRouting();
    //app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    ...

    The call order had to be changed a little to move UseAuthentication before UseRewriter (which comes before UseRouting). UseRewriter needs the claims to be set, and UseRouting needs the request path to be changed already.

    Then in the AccountController POST method, after logging in, I do this:

    Request.Path = (PathString)HttpContext.Items["OriginalRequestPath"];
    return Redirect(Request.GetEncodedUrl());
    

    Plus a little error checking, and it works fine.

    Tuesday, June 2, 2020 7:21 PM