Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

ASP.NET MVC Core - returnurl is always null #5242

Closed
saf-itpro opened this issue Sep 6, 2016 · 13 comments
Closed

ASP.NET MVC Core - returnurl is always null #5242

saf-itpro opened this issue Sep 6, 2016 · 13 comments
Assignees
Labels

Comments

@saf-itpro
Copy link

saf-itpro commented Sep 6, 2016

In my ASP.NET MVC Core web app with Individual User Account authentication, the returnurl in the default login() get and post action methods (shown below) is always null. I verified it by placing a breakpoint after the line ViewData["ReturnUrl"] = returnUrl;. When I login to the app I see at the breakpoint that returnUrl is always null. Why is that even though I can see that the corresponding form tag has asp-route-returnurl="@viewdata["ReturnUrl"]" attribute value defined? I see the same null behavior in other action methods where I'm using returnUrl and making sure that the corresponding form tags of these action methods have asp-route-returnurl="@viewdata["ReturnUrl"]" attribute defined.

AccountController:

[Authorize]
public class AccountController : Controller
{
    // GET: /Account/Login
    [HttpGet]
    [AllowAnonymous]
    public IActionResult Login(string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        return View();
    }
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        if (ModelState.IsValid)
        {
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, set lockoutOnFailure: true
            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                _logger.LogInformation(1, "User logged in.");
                return RedirectToLocal(returnUrl);
            }
            if (result.RequiresTwoFactor)
            {
                return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
            }
            if (result.IsLockedOut)
            {
                _logger.LogWarning(2, "User account locked out.");
                return View("Lockout");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return View(model);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }
}

login.cshtml view:
`<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@viewdata["ReturnUrl"]" method="post" class="form-horizontal">
.....//rest of the html removed for brevity
.....

`
@rynowak
Copy link
Member

rynowak commented Sep 6, 2016

What's the output of the form tag look like in the generated HTML? - is the returnurl part of the query string for the form?

@saf-itpro
Copy link
Author

@rynowak The generated HTML for form tag is shown below. The app logs me in correctly and shows Hello MyLoginName and logoff links at the top right corner of the page after I successfully log in.

<form method="post" id="logoutForm" class="navbar-right" action="/Account/LogOff">
        <ul class="nav navbar-nav navbar-right">
            <li>
                <a title="Manage" href="/Manage">Hello myLoginName@myDomain.com!</a>
            </li>
            <li>
                <button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button>
            </li>
        </ul>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Mi8s83lkBNHlpnJILYJXeOWmopD7NdTI4hAqL49NZIbrM-Ouj7T6ciNW4As8kh34GIIeGXqHvVq4YmPYpan_cD28fXXDTRmGqxTGqYAreAh3U_5rZtyd3ZSv_YXG-zgA3vp_QdtLY1K47G6wCSaX9piRMQrnoDOMGxQu6-7bAbX5D0LGvuEESH8eufrk3h0kw" />
</form>

@rynowak
Copy link
Member

rynowak commented Sep 7, 2016

@dougbu @NTaylorMullen - something funky is going on here with the taghelper generating the URL.

@dougbu
Copy link
Member

dougbu commented Sep 7, 2016

@saf-itpro by default, RequiresTwoFactor is false for the Individual User Account scheme. Have you changed that in your project? If not, returnUrl is only ever set if it's model bound and I don't see the <form> submitting such a value.

If I haven't hit on the problem, please provide a full repro project. Ideally upload something small to a public GitHub repo.

@saf-itpro
Copy link
Author

@dougbu Let me check what you are suggesting. But to note: I just created a project in VS2015-Update3 using a default template ASP.NET Core Web Application (.NET Core) and choosing the authentication to be Individial User Accounts. And, noticed this behavior on Get and Post action methods named Login(..) and Register(). I did not change anything in the test project that I created. One can test this behavior by creating a test project as I described above.

@saf-itpro
Copy link
Author

@dougbu I did not see an option to change RequiresTwoFactor value. I do not know how to upload the project in public GitHub repo. But following are the steps I used to create the project and still the same returnurl issue: This seems to be a bug or please let me know reason why returnurl returns null value. Part of AccountController.cs and login.cshtml view are given in the original post above.

  1. In VS2015-Update3, created a new project using ASP.NET Core Web Application (.NET Core) project template and choosing Individual User Accounts authentication.
  2. Ran the command in Package Manage console: PM> update-database -context ApplicationDbContext. Note: This command creates necessary ASP.NET Identity tables ASPNETUsers, etc in LocalDb
  3. Successfully compiled the app and ran the app in debug mode and performed the test as follows:
  • Placed a breakpoint in Login(...) and Register(...) Get/Post action methods.
  • Clicked on Register link on the upper right corner of the home page and noticed that in the Register(...) get method the returnurl was null. After entering the new username password info clicked on Register button and noticed that the returnurl variable was still null in the post Register(...) method.
  1. After successfully registering the username/password, clicked on the login link in the upper right corner of the home page and repeated the same test [as for Register(....) action methods above] for Login(...) Get/Post action methods. The reutrnrul variable was null there as well.

@javiercn
Copy link
Member

javiercn commented Sep 8, 2016

The return url is null because you are trying to log in / register directly. Try to access a protected resource (an action with [Authorize] on it and you'll see that the return url points to the original resource you were trying to access to.

@dougbu
Copy link
Member

dougbu commented Sep 8, 2016

@saf-itpro did @javiercn's comment help you solve the problem? I won't actively investigate 'til I hear back.

@saf-itpro
Copy link
Author

saf-itpro commented Sep 9, 2016

@javiercn I tried your suggestion as shown below. But still returnUrl is null. Steps are as follows:

  1. Created this exact web app from official ASP.NET Website EXCEPT that I chose Individual User Accounts authentication
  2. RanPM> update-database -context ApplicationDbContext to create ASP.NET Identity tables in LocalDb.
  3. Added asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" attributes to the form element of Create.cshtml file
  4. Created a link on the Home page as: <a asp-controller="Movies" asp-action="Create">Test</a>
  5. Added [Authorize] on top of the MoviesController shown below. Added string returnUrl = null as input parameters in Create(...) Get/Post methods. Added ViewData["ReturnUrl"] = returnUrl; in those methods. Placed a breakpoint inside Create(..) Get/Post methods.
  6. Ran the app in Debug Mode. Successfully registered and logged in. Clicked on the link of step3 above.
  7. Observed in debug mode that the returnUrl in Create(...) (both Get and Post methods) was still null. However the app ran successfully and I even created some test movies.
[Authorize]
    public class MoviesController : Controller
    {
        private readonly ApplicationDbContext _context;

        public MoviesController(ApplicationDbContext context)
        {
            _context = context;    
        }

        // GET: Movies/Create
        public IActionResult Create(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }

        // POST: Movies/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            if (ModelState.IsValid)
            {
                _context.Add(movie);
                await _context.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            return View(movie);
        }
}

View Source of the form element of Create.cshtml:

<form method="post" action="/Movies/Create">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger"></div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="Genre">Genre</label>
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true" />
            </div>
        </div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="Price">Price</label>
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true" />
            </div>
        </div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
            <div class="col-md-10">
                <input class="form-control" type="datetime" data-val="true" data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" value="" />
                <span class="text-danger field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true" />
            </div>
        </div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="Title">Title</label>
            <div class="col-md-10">
                <input class="form-control" type="text" id="Title" name="Title" value="" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true" />
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Mi8s83lkBNHlpnJILYJXeMZZmICDdXy6j7XThwq9heJ2hCsR3CkoRZdOw_5EdkkFfOX0EGa-qwP1crbZA-itgKjUrYvGt3nsMkMB-4GttfahG27OKEtSX1s2jyEK2KAH8Lp-4xfNbnQ9g90pMM2dgkv_rmpClnnQYZ5KMT0VJgkwLPwngve5_QBVyyQVXj3sA" /></form>

@saf-itpro
Copy link
Author

@dougbu I'm still getting returnUrl as null. The test process (suggested by @javiercn ) is described above.

@dougbu
Copy link
Member

dougbu commented Sep 26, 2016

Hmm, thought I commented on this issue a while ago.

I cannot reproduce the behaviour you describe. If I'm currently logged out and navigate to a page requiring a logged-in user, returnUrl is never null. That is returnUrl works when it's supposed to work.

Note there's no reason to change the created individual accounts project e.g.

  1. Start the new site
  2. Register
  3. Log out
  4. Enter http://localhost:5000/Manage in your browser

Note you're redirected to http://localhost:5000/Account/Login?ReturnUrl=%2FManage

@saf-itpro
Copy link
Author

@dougbu So, once you logged in returnUrl will always be null. In an ASP.NET MVC project, when you decorate a class or method with [Authorize] and authorization fails, the site automatically redirects to the login page (using the loginUrl specified in web.config). In addition, something in the ASP.NET MVC framework passes along the original request's URL as a ReturnUrl parameter.

@akberc
Copy link

akberc commented Sep 29, 2017

FWIW: I had the exact same symptom when moving from a Windows development machine to Linux test machine, with a copied database that did not have ASP identity tables. At first, I thought it was case sensitivity, however, once the tables were restored, everything worked exactly as it did on the development machine.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

6 participants