Reputation: 9193
I've started a new MVC 5 site, using the new Asp.Net Identity with Owin. In my "account" controller which has the attribute [Authorize], I have fairly standard actions;
// GET: /User/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
// POST: /User/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
try
{
if (ModelState.IsValid)
{
var userApi = new UserService();
var apiUser = await userApi.LogIn(UserManager, model.CardNumber, model.Pin, model.RememberMe);
if (apiUser != null)
{
await SignInAsync(apiUser, model.RememberMe);
if (string.IsNullOrEmpty(returnUrl))
{
return RedirectToAction("UserLoggedIn", "User");
}
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
}
catch (Exception ex)
{
Trace.TraceError("Cannot login {0}", ex.ToString());
Response.AppendToLog(ex.ToString());
ModelState.AddModelError("", ex.ToString());
}
// If we got this far, something failed, redisplay form
return View(model);
}
My question is in regards to the returnUrl behavior, the code above works in the sense that, if a user is not logged in and calls a action in a controller that has the attribute [Authorize], it gets sent to the login actions above and then returned to the controller/action that was requested. Which is great, BUT how?? And is it safe?
In this article about "Preventing open redirect attacks"(for earlier versions of Asp.Net MVC) the recommendation is to do a check on the returnUrl that it's a local url before doing the redirect, is that something I should still do or is it now handled by the framework?
Cheers, Ola
Upvotes: 12
Views: 42427
Reputation: 500
As Sandeep Phadke told, the returnUrl Parameter is filled, because of configuration in startup.Auth.cs.
The CookieAuthenticationOptions has a property ReturnUrlParameter which is by Default set to "returnUrl". That is the reason, why it looks like magic. You can Change it to whatever you want:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ReturnUrlParameter = "returnTo"
});
Then you can Change the AccountController Login-Action to:
[AllowAnonymous]
public ActionResult Login(string returnTo)
{
ViewBag.ReturnUrl = returnTo;
return View();
}
Upvotes: 5
Reputation: 882
To answer your first question on how the redirect Url is setup, it configured in Startup.Auth.cs
which is called from Startup.cs
and is marked with an attribute which is probably looked for by the OWIN framework on app startup and both files partial
extend a Startup
class.
In Startup.Auth.cs
there's a class to configure authentication options and usually has the following code
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieSecure = CookieSecureOption.Always
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// ....
// I deleted code which is commented out if you selected "Individual accounts"
// if you created the site project using the VS 2013 wizard
// ...
}
}
I added the CookieSecure
option to ensure cookies were signed and that is recommended as a good security practice, other than that its boiler plate code.
More documentation on CookieAuthenticationOptions if you want it.
Upvotes: 2
Reputation: 4259
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Controller");
}
Upvotes: 3
Reputation: 3392
You need to check if the url is local indeed using this method (it is not handled by the framework automatically): http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.islocalurl%28v=vs.118%29.aspx
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
Upvotes: 18