ASP.NET MVC Authorization and hyperlinks - asp.net-mvc

I am using successfully custom authorization in ASP.NET MVC. It simply involves a comparison between User.Identity and the owner of the object in context.
It works like a charm when used in simple conditions. It becomes more complicated when I try to call 2 actions in one web request.
Lets say I want to display an image which would be generated on-the-fly by my application. This image is generated by a controller, thus, it can be referenced by an URL even if it doesn't exist physically. I have decided that the user must be signed in and be the owner to view it, so I apply my authorization mechanizm to it.
Example: <img src="http://myapplication.com/images/generate/3" />
When I include such an image in a page via its action hyperlink, I expect that the authenticated user will still be in context on server side when the image is generating. This is not the case in my tests. The image never displays because my authorization check doesn't work. In the image controller, User.Identity is empty as if the user has not signed it.
In the meantime, the same user is still signed in to the website and can continue to browse with his identity in context... without those images working properly.
I wonder how to make this process work securely...
Thank you very much!
Marc Lacoursiere
RooSoft Computing inc.

Just wondering if you've checked if
Thread.CurrentPrincipal
is also empty in the controller? It should contain the same value.
Another suggestion would be to store the User.Identity value in a session?

You need to set up your identity in global.asax on every request. I'm using a custom Principal and Identity for this.
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
SetIdentity(new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue });
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Repository.GetIdentity
(authTicket.Name, new HttpRequestWrapper(Request));
SetIdentity(identity);
}
}
}
private void SetIdentity(MyIdentity identity)
{
Context.User = new MyPrincipal { Identity = identity };
Thread.CurrentPrincipal = Context.User;
}
This works, but I don't guarantee it to be secure. You should review this article on FormsAuthentication vulnerabilities prior to going live with this code. You have to understand that this code assumes the cookie is valid and hasn't been hijacked. There are some additional security measures that can be taken to reduce these vulnerabilities which this code doesn't show.

This may be when the site link in browser is http:\www.mysite.com (or http:\subdomain.mysite.com ) and you are using http:\mysite.com\image\5 in your application. Form authentication uses cookies. And these cookies may belong to domains and subdomains.
To find out what is going on I suggest to use FireFox with FireBug installed. Enable Net and Console tab for your site and make a complete refresh of the page. After you'll see requests in one of these tabs (Net tab exactly). At the left of the request you can see 'a plus' button, after you click it you'll see Headers and Response tabs (more detailed description of firebug). Have a look at Headers tab and try to find something like FORMAUTH (or what you've set in config as a forms cookie name). If you not see it - the problem is in domains.

Related

Dynamic redirect url from google console - oAuth

I have created a MVC/API project to enable external authentication and worked fine for my local host url. However, I need to achieve the below.
I am supporting multi tenancy (same app service and different DB), so each tenant has to connect different DB based on the custom param in the MVC url
Ex: https://localhost/tenant1, .../tenant2, .../tenant3 etc (not going with separate subdomain at this point)
I am not sure if the Google Console supports the wildcard url as a return ur and not sure how to achieve that in MVC code (Ex:http://localhost/* OR {0} .. something like that. (So dynamic input parameter will be returned back from google)
I am reading and attempting some solutions. Will update the answer here once i get the complete solution. In the meantime if anyone has any suggestions, please help me.
UPDATE 1:
I have updated my source code as follows:
Create session object before redirecting to the external login
System.Web.HttpContext.Current.Session["Tenant"] = "tenantname";
After callback read the tenant details and save in the session for subsequent DB calls based on the tenant name
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
if (System.Web.HttpContext.Current.Session["Tenant"] != null)
{
string sessionObj = System.Web.HttpContext.Current.Session["Tenant"] as String;
}
This is a common requirement, and is easily solved. There are two components.
Firstly, regardless of which of your many URLs your application lives at (myapp.com/tenant1, /tenant2, etc) you have a single redirect URL (eg myapp.com/oauthredirect).
Secondly, when starting the OAuth dance (https://developers.google.com/identity/protocols/OAuth2WebServer#redirecting), you can specify a state parameter which will be passed into your oauthredirect routine (eg. as state=tenant1). You can then use this to create a redirect back to the appropriate site URL once you have finished your user registration tasks.
Be careful when specifying your redirect URLs into the developer console. They must be a character-by-character match with the actual URL. So, foe example, you will need to specify both http://myapp.com/oauthredirect and https://myapp.com/oauthredirect. I've always found it quite useful to create a local entry in /etc/hosts (or the windows equivalent) so your localhost is also resolved by eg. http://test.myapp.com
Authorized redirect URIs For use with requests from a web server. This
is the path in your application that users are redirected to after
they have authenticated with Google. The path will be appended with
the authorization code for access. Must have a protocol. Cannot
contain URL fragments or relative paths. Cannot be a public IP
address.
http://localhost/google-api-php-client-samples/Analytics/Oauth2.php
http://localhost/authorize/
You can have as many of them as you want but the wild card is not going to work.

How to perform one time action on first login using ASP.NET MVC5 Windows Authentication

I am creating web application using ASP.NET MVC5 framework with 'windows authentication' - i.e. I am creating an intranet application authenticating users against active directory.
When new user is defined in company's active directory, I'd need to catch his first login and redirect him onto profile page, where user is prompted to fill some info.
I am able to catch user's first login by simple look into the database, if the user has here his own record in some table. If not, the user is here first time and I can create him such record.
Here comes the deal - after extensive searching through possibilities it seems the only "reasonable" way how to do it is via custom AuthenticationFilter - specifically to put the DB check logic into
OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
method.
The reason why I put "reasonable" into the quotes is that on one hand, this approach fits into the MVC philosophy - by which I mean that it is not some 'hack' approach.
On the other hand - since I am using windows authentication, there is effectively no Login action in any controller. User can type whatever 'www.mysite.com/controller/action' url and if not logged in, there is no redirect to login page, the windows security box just appears prompting for credentials. Which means that I have to register my custom authentication filter globally to cover all controller/action patterns. That would mean that the DB check would be performed each and every request - I don't like this. I am not sure how much performance hit this could make, but also it doesn't seem right from design point of view.
The last thing I've tried was to use my authentication filter to catch 'unauthenticated' users and redirect them to some 'Login' action - but here I found that the windows security box is appearing before even the authentication filter is fired, so technically my custom authentication filter never catches an unauthenticated user.
So my question is - is there a better approach how to step in the logging process with one time action? Or I can use what I have - i.e. globally registered authentication filter performing DB check every request ?
Thanks
Finally I have some working solution for this.
The problem:
MVC5 APS.NET intranet application using windows authentication. After successful login against active directory we want to find out if the user is here for the first time and if so, create him record in application database.
The solution:
Since I am at the end of the day interested only in authenticated and authorized users I've created a globally registered action filter i.e. filter which will be applied to every single controller/action combination after successful authentication/authorization.
Inside this filter I am checking if the current session has flag IsNewSession set to true. If so, I am performing that check against application DB. That way even if the action filter is invoked each request I am doing roundtrip into database only once - during user's first request.
The implementation:
public class DbCheckFilter : ActionFilterAttribute
{
private AppDbContext db = new AppDbContext();
//we are overriding OnActionExecuting method since this one
//is executed prior the controller action method itself
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//is this a new session
if (filterContext.HttpContext.Session.IsNewSession)
{
//we are storing users in db based on their active directory
//Guid - therefore we need to get 'UserPrincipal' object
//instead of 'WindowsPrincipal' provided by filterContext
using (var principalContext = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(principalContext, filterContext.HttpContext.User.Identity.Name);
if (principal != null)
{
//finally we perform the DB check itself
if (!CreateUserInDbIfNew(principal.Guid.Value, principal.DisplayName))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
base.OnActionExecuting(filterContext);
}
I believe this is what you're looking for. Your db check will only happen once at this point. You can add this method to your Global.asax like so, which works with Windows Auth once authorized...
protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
// Do your check here
// Do something
}

how to verify referrer inside a MVC or Web Api ajax call

my MVC app has common ajax methods (in web api and regular controller). I'd like to authorize these calls based on which area (view) of my app the call is coming from. The problem I am facing is how to verify the origin of the ajax call.
I realize that this is not easily possible since ajax calls are easy to spoof, but since I have full control of how the view gets rendered (full page source) perhaps there is a way to embed anti-forgery type tokens that could later be verified to a Url Referrer.
Authentication is already handled and I can safely verify the identity of the call, the only problem is verifying which URL (MVC route) the call came from. More specifically, preventing the user from being able to spoof the origin of the ajax call.
I tried creating a custom authorization header and passing it between view render and ajax calls, and that works, but still easy to spoof (since a user could sniff the headers from another part of the site and re-use those). In the end I am not sure how to safely verify that the header has not been spoofed. The only thing that comes to mind is encoding some info about the original context inside the token, and validating it somehow against incoming call context (the one that's passing the token in ajax call).
I see that MVC has AntiForgery token capabilities, but I am not sure if that can solve my problem. If so I'd like to know how it could be used to verify that /api/common/update was called from /home/index vs /user/setup (both of these calls are valid).
Again, i'd like a way to verify which page an ajax call is coming from, and user identity is not the issue.
update
as per #Sarathy recommended I tried implementing anti-forgery token. As far as I can tell this works by adding a hidden field with token on each page, and comparing it to a token set in a cookie. Here is my implementation of custom action filter attribute that does token validation:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers["X-Request-Verification-Token"];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("filter \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
base.OnActionExecuting(filterContext);
}
then my anti forgery additional data provider looks like this:
public class MyAntiForgeryProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(System.Web.HttpContextBase context)
{
var ad = string.Format("{0}-{1}",context.Request.Url, new Random().Next(9999));
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.AdditionalData: {0}", ad);
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.UrlReferrer: {0}", context.Request.UrlReferrer);
return ad;
}
public bool ValidateAdditionalData(System.Web.HttpContextBase context, string additionalData)
{
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData Request.Url: {0}", context.Request.Url);
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData additionalData: {0}", additionalData);
return true;
}
this works, in that i can see correct pages logged in the provider, and anti forgery breaks w/out the tokens.
however, unless i did something wrong, this seems trivial to spoof. for example
if i go to pageA and copy the token form pageB (just the form token, not even the cookie token), this still succeeds, and in my logs i see pageB while executing ajax method from pageA
confirmed that this is pretty easy to spoof.
I am using csrf to generate ajax tokens like this:
public static string MyForgeryToken(this HtmlHelper htmlHelper)
{
var c = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
string cookieToken, formToken;
AntiForgery.GetTokens(c != null ? c.Value : null, out cookieToken, out formToken);
return formToken;
}
I then pass the form token back with each ajax call and have a custom actionfilterattribute where I read/validate it along with cookie token
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers[GlobalConstants.AntiForgeKey];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("MyAntiForgeryAttribute.OnActionExecuting. \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
this all works (changing anything about the token throws correct exception), then in my IAntiForgeryAdditionalDataProvider I can see what it thinks it's processing.
as soon as i override the csrf token from another view, it thinks it's that view. I don't even have to tamper with the UrlReferrer to break this :/
one way this could work if i could force the cookie to be different on every page load
I am assuming you can use IAntiForgeryAdditionalDataProvider for this.
public class CustomDataProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(HttpContextBase context)
{
// Return the current request url or build a route or create a hash from a set of items from the current context.
return context.Request.Url.ToString();
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
{
// Check whether the allowed list contains additional data or delegate the validation to a separate component.
return false;
}
}
Register the provider in App_Start like below.
AntiForgeryConfig.AdditionalDataProvider = new CustomDataProvider();
https://msdn.microsoft.com/en-us/library/system.web.helpers.iantiforgeryadditionaldataprovider(v=vs.111).aspx
Hope this helps in your scenario.
You mentioned in your question that you're looking for Anti-forgery token capabilities.
Hence, I think what you're asking about is an anti-CSRF solution (CSRF=cross site request forgery).
One way to do this is to render a true random number (a one-time token) into your page, then passing it on each request, which can be done by adding a key/value pair to the request header and then checked at the backend (i.e. inside your controller). This is a challenge-response approach.
As you mentioned, in the server-side code you can use
var fToken = req.Headers["X-Request-Verification-Token"];
to get it from the requesting page.
To pass it along from each client AJAX request of the page, you can use
var tokenValue = '6427083747'; // replace this by rendered random token
$(document).ajaxSend(function (event, jqxhr, settings) {
jqxhr.setRequestHeader('X-Request-Verification-Token', tokenValue);
});
or you can set it for each request by using
var tokenValue = '2347893735'; // replace this by rendered random token
$.ajax({
url: 'foo/bar',
headers: { 'X-Request-Verification-Token': tokenValue }
});
Note that tokenValue needs to contain the random number which was rendered by the web server when the web page was sent to the client.
I would not use cookies for this, because cookies don't protect you against CSRF - you need to ensure that the page, which is requesting is the same as the page which was rendered (and hence created by the web server). A page being on a different tab in the same browser window could use the cookie as well.
Details can be found on the OWASP project page, in the OWASP CSRF prevention cheat sheet.
My quick interim solution was to use custom tokens created on each page load (guid which i keep track of in my token cache), which are passed as headers in all ajax calls. Additionally i create a original url hash and combine it into the custom auth token.
in my ajax methods I then extract the hash and compare it with UrlReferrer hash to ensure that hasn't been tampered with.
since the custom token is always different it's less obvious to guess what's going on as token appears to be different on every page load. however this is not secure because with enough effort the url hash can be uncovered. The exposure is somewhat limited because user identity is not the problem so worst case is a given user would gain write access to another section of the site but only as himself. My site is internal and i am auditing every move so any temper attempts would be caught quickly.
I am using both jQuery and angular so appending tokens with all requests like this:
var __key = '#Html.GetHeaderKey()' //helper method to get key from http header
//jQuery
$.ajaxSetup({
beforeSend: function (xhr, settings) {
xhr.setRequestHeader('X-Nothing-To-See-Here', __key); // totally inconspicuous
})
//angular
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Nothing-To-See-Here'] = __key;
});
update
the downside of this approach is that custom tokens need to be persisted across a web farm or app restarts. Based on #Sarathy's idea I am trying to side step this by leveraging MVC anti forgery framework. Basically add/remove my "salt" and let the framework manage the actual token validation. That way it's a bit less to manage for me. Will post more details once i verify that this is working.
So this is going to be one of those "you're doing it wrong" answers that I don't like, and so I apologize up front. In any case, from the question and comments, I'm going to propose you approach the problem differently. Instead of thinking about where did the request come from, think about what is the request trying to do. You need to determine if the user can do that.
My guess as to why this is hard in your case is I think you have made your api interface too generic. From your example api "api/common/update" I'm guessing you have a generic update api that can update anything, and you want to protect updating data X from a page that is only supposed to access data Y. If I'm off base there then ignore me. :)
So my answer would be: don't do that. Change your api around so it starts with the data you want to work with: api/dataX api/dataY. Then use user roles to protect those api methods appropriately. Behind the scenes you can still have a common update routine if you like that and it works for you, but keep the api interface more concrete.
If you really don't want to have an api for each table, and if its appropriate for you situation, perhaps you can at least have an api for protected/admin tables and a separate api for the standard tables. A lot of "if"s, but maybe this would work for your situation.
In addition, if your user can update some dataX but not other dataX, then you will have to do some sort of checking against your data, ideally against some root object and whether your user is authorized to see/use that root object.
So to summarize, avoid an overly generic api interface. By being more concrete you can use the existing security tools to help you.
And good luck!

How do you define the login page you want unauthorized user to be redirected to

I have decorated my controller with an Authorize attribute, as so:
[Authorize(Roles="ExecAdmin")]
If I try to go to that controller after logging in as a user who is not ExecAdmin, it does appear to be attempting to redirect to a login page. BUT, the page it is attempting to redirect to is not my login page, it is a view called LogOnUserControl.ascx. This is a partial view that is not displayed by my login page.
I have no idea why it is doing this -- or maybe it is trying to redirect to some other page altogether, one which does display LogOnUserControl.ascx. Or maybe it is looking for anything with "LogOn" in the name? (Though the name of my login view is LogOn.aspx...)
How can I tell it what page to redirect to?
UPDATE: I do have this in the global.asax
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(new char[] { ';' });
//Context.ClearError();
if (Context.User != null)
{
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}
}
... since I am using a non-standard way of defining roles; i.e., I am not using ASP.NET membership scheme (with role providers defined in web.config, etc.). Instead I am setting roles this way:
// get user's role
string role = rc.rolesRepository.GetUserType(rc.loginRepository.GetUserID(userName)).ToString();
// create encryption cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(120),
createPersistentCookie,
role //user's role
);
// add cookie to response stream
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
(This is called after the user has been validated.)
Not sure how this could be impacting the whole thing, though ...
UPDATE: Thanks to Robert's solution, here's how I solved it -- extend AuthorizeAttribute class:
public class AuthorizeAttributeWithMessage : AuthorizeAttribute
{
private string _message = "";
public string Message
{
get {
return _message;
}
set {
_message = value;
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
// user is logged in but wrong role or user:
filterContext.Controller.TempData.Add("Message", Message);
}
base.HandleUnauthorizedRequest(filterContext);
}
}
Then in the LogOn view:
<%
if (HttpContext.Current.Request.IsAuthenticated)
{
// authenticated users should not be here
Response.Redirect("/Home/Index");
}
%>
And in the home page view:
<% if (TempData != null && TempData.Count > 0 && TempData.ContainsKey("Message"))
{ %>
<div class="largewarningtext"><%= TempData["Message"]%></div>
<% } %>
And atop the affected controllers:
[AuthorizeAttributeWithMessage(Roles = "Consultant,ExecAdmin", Message = "You do not have access to the requested page")]
This has the advantage of ALWAYS redirecting any authenticated user who ends up on Logon.aspx -- authenticated users should not be there. If there is a message in the TempData, it will print it out on the home page; if not, it will at least have done the redirect.
Login page is configured within web.config file.
But you probably already know that. The real problem here is a bit more complicated. I guess you're onto something very interesting here, since Login page barely authenticates a user. It doesn't check its authorization for a particular resource (which is your case here where authorization fails) so this shouldn't redirect to login page in the first place.
Checking AuthorizeAttribute source code, you should get a 401: Unauthorize Request response from the server. It doesn't redirect you to the login page (as I anticipated in the previous paragraph, since login is too stupid for that. So there most be something else in your code that doesn't work as it should.
Edit
As this page states:
If the site is configured to use ASP.NET forms authentication, the 401 status code causes the browser to redirect the user to the login page.
Based on this information it's actually forms authentication that sees this 401 and redirects to login (configured as you described in the comment).
But. It would be nice to present some message to the user why they were redirected to login page in the first place. No built-in functionality for that... Still this knowledge doesn't solve your problem, does it...
Edit 2
There are two patterns you can take that actually look very similar to the user, but work diferently on the server.
Simpler one
Write your own authorization attribute (simply inherit from the existing one and add an additional public property Message to it), where you can also provide some sort of a message with attribute declaration like ie.
[AuthorizeWithMessage(Role = "ExecAdmin", Message = "You need at least ExecAdmin permissions to access requested resource."]
Your authorization attribute should populate TempData dictionary with the provided message (check documentation about TempData that I would use in this case) and then call into base class functionality.
change your login view to check for the message in the TempData dictionary. If there is one, you can easily present it to the already authenticated user (along with a link to some homepage that they can access), so they will know why they are presented with a login.
Complex one
create your own authorization filter (not inheriting from the original) and provide your own redirection to some authorization login view that would serve the login in case user has insufficient rights.
create your custom login view that can in this case be strong type. Your authorization filter could populate it with the correct model. This model will include the message string and it can also provide the route link to a page where a user can go.
custom configuration classes that serve this configuration of custom login page.
You could as well configure various different route definitions based on user rights. So for some rights they'd be presented with some page, but if they have some other rights, their route would point to a different route.
Which one to choose?
Go with the simpler one if it satisfies your needs, but if you want more control of the whole process I'd rather go with the complex one. It's not so complicated and it would give you full control of the insufficient login process. You could make it a much better experience for the users.

Cookieless sessions and cross-site form posts

Here's the situation...
Site 1) ASP.NET MVC application for customers to login, view and pay bills, etc. This site is using cookieless ASP sessions to allow multiple sessions to run in tabbed browsers.
Site 2) Corporate web site with standard static content. This will have a small form at the top of each page with a username/password form that will post to the MVC application.
When posting to the MVC app, a new session is being generated and the site is returning a 302 redirect with the session ID in the URL (as expected). The controller has two Login methods, one for handling a GET, one for handling a POST. Because of the redirect, it's hitting the GET method and it loses the form values.
By changing the method on the corporate site to do a form GET rather than a POST, the username and password are preserved in the query string after the redirect and I could handle the requests that way, but I would rather do a POST and not pass that data around in the URL.
My gut says that implementing some sort of custom HttpHandler would allow me to do this, but I'm not sure where I'd be able to tie into the session creation. A breakpoint at Session_Start in global.asax shows that the session ID has already been crated and the redirect already happened at that point.
I believe I found where the redirect was happening as well as a possible solution to it. The ISessionIDManager interface has a SaveSessionID method with an "out bool redirected" parameter on it. It appears that when a new session is created, the default session manager rewrites the URL inside this method and does a redirect. I played around with implementing my own ISessionIDManager and I was able to suppress the redirect, but there's no way (that I could tell) of inserting the session ID into the URL for the cookieless session without doing the redirect. Server.Transfer is not allowed at that point in the request lifecycle. I tried that but received an "Error Executing Child Request" message.
Later on, I found this article on Enabling POST in cookieless ASP.NET Applications. Basically by tying in to the Application_EndRequest event, you can detect the redirect, clear the response and fake a form post to the new URL that contains the session ID.
void MvcApplication_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if (application.Request.Form["CorpLogin"] == "1"
&& application.Response.RedirectLocation != null
&& application.Response.IsRequestBeingRedirected)
{
StringBuilder build = new StringBuilder();
build.Append("<html>\n<body>\n<form name='Redirect' method='post' action='");
build.Append(application.Response.RedirectLocation);
build.Append("' id='Redirect' >");
foreach (string key in application.Request.Form)
{
if (key != "CorpLogin")
build.Append(string.Format("\n<input type='hidden' name='{0}' value = '{1}'>", (string)key, application.Request.Form[(string)key]));
}
build.Append("\n<noscript><h2>Object moved <input type='submit' value='here'></h2></noscript>");
build.Append(#"</form>
<script language='javascript'>
<!--
document.Redirect.submit();
// -->
</script>
");
build.Append("</body></html>");
application.Response.Clear();
application.Response.ClearHeaders();
application.Response.Output.Flush();
application.Response.Write(build.ToString());
}
}
This isn't an ideal solution IMHO but it might be workable enough to meet the requirements.
When the MVC site issues the redirect are you handling that yourself with a controller or is it being routed automatically by your membership provider?
If you can intercept the request before the redirect you could place the username and password in the TempData variable and process it after the redirect.
You're looking for a Server.Transfer equivalent in MVC (see Simon's answer.)
It's like Request.Redirect but simply transfers the .NET processing to another page. It also allows for maintaining the form variables even on POST between transfers (in this case a RouteValueDictionary).
EDIT: (response to your comment #1)
Here is an example of Forms Authentication with MVC. Without knowing the internals of your authorization mechanism, I'm not entirely sure. At any point of the "DoLogin" process, you would be able to do a transfer instead of a redirect.

Resources