ASP.Net Session changes between AJAX calls to ASPX WebMethod - c#

I have a single page application that starts off with default.aspx creating the basic HTML layout and then I use AJAX calls to get data from static WebMethods in other aspx pages. I also have a KeepAlive.aspx that reloads every once in a while to keep the session alive.
The login form that appears initially calls the login webmethod which uses an external web service to login and store user information in the session. I use HttpContext.Current.Session to get and set values in the session.
The very next web method that I call is to getuserpreferences from the external web service using identity information obtained at login time.
This is all working fine and dandy on various IIS servers. Recently we deployed this on a new server and it is not working there.
It does successfully login and saves the user values in session, but when the next ajax call to getuserpreferences happens, the getuserpreferences web method tries to get the session values... and it is NOT there any more!
I put in a bunch of logging and saw that session Id is changing! The login session id is different from the session id that I see from get userpreferences, even though both use HttpContext.Current.Session.
On the servers that it does work, sometimes (randomly?) it does behave in a similar way: it loses session values and throws me back to login.
Please provide help/tips to trace this issue and ensure the same session continues across ajax calls. This is not an MVC or WebAPI project; it is a simple aspx application.
UPDATE (Jan 8): I found the difference in behavior between the instances where this works and where it doesn't: ASP.Net_SessionId cookie value is being set where it works, but in this case it is not being set and is therefore not maintaining session state. Now I need to figure out why it is not setting.
Update 2 (Jan 8): When we installed a certificate on the server and implemented https, the session Id cookie started appearing. So although the issue is no longer urgent, I do want to find out why HTTP did not work.
Code:
WebServiceHelper.js
WebServiceHelper.prototype.invokeServiceMethod = function(serviceName, methodName, params, returntype) {
var self = this;
var obj = {};
var def = $.Deferred();
var url = serviceName + "/" + methodName;
$.support.cors = true;
$.ajax({
url: url,
data: ko.toJSON(params),
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(mydata) {
if (mydata && mydata.d) {
def.resolve(mydata.d);
}
},
error: function(msg) {
def.reject(msg);
}
});
return def.promise();
};
Data Provider.js:
DataProvider.prototype.loginUser = function(email, password, token) {
var self = this;
var def = $.Deferred();
var webServiceHelper = new WebServiceHelper();
webServiceHelper.invokeServiceMethod("Login.aspx", "LoginUser", { email: email, password: password, token: token }, "json").done(function (retObj) {
self.userObj = retObj.userinfo;
self.loginTime = new Date();
self.loadInitialData();
def.resolve();
}).fail(
function(retObj1) {
def.reject(retObj1);
});
return def.promise();
};
Preference Manager.js
PreferenceManager.prototype.invokeGetPreferences = function (prefKey) {
var self = this;
var def = $.Deferred();
var webServiceHelper = new WebServiceHelper();
webServiceHelper.invokeServiceMethod("WebMethods.aspx", "GetPreferences", { key: prefKey }, "json").done(function (retObj) {
def.resolve(retObj);
}).fail(
function (retObj1) {
def.reject(retObj1);
});
return def.promise();
};
Login.aspx.cs:
[WebMethod(EnableSession=true)]
public static string LoginUser(string email, string password, string token)
{
if (Authenticate(email, password, token))
{
HttpContext.Current.Session["vanalyticsLastLoginDateTime"] = DateTime.Now;
var userjson = GetUserJson();
Logger.Debug("Login - Session Id {0} - Returning user json {1}", HttpContext.Current.Session.SessionID, userjson);
return userjson;
}
return "Error: " + _validationError;
}
private static bool Authenticate(string stremail, string strpassword, string token)
{
var vplug = new vPlugin();
HttpContext.Current.Session.Remove("vUserInfo");
vplug.Authenticate(HttpContext.Current, appsurl, stremail, strpassword, token);
_validationError = vplug.LastException != null ? vplug.LastException.Message : null;
return (HttpContext.Current.Session["vUserInfo"] != null);
}
vPlugin code: (eventually calls setuser)
private void SetUser(HttpContext obj, userInfo user)
{
HttpSessionState session = obj.Session;
session["vUserInfo"] = user;
....
session["vDataToken"] = setting.ContainsKey("token") ? setting["token"] : "0-0";
}
WebMethods.aspx:
[WebMethod(EnableSession=true)]
public static string GetPreferences(string key)
{
var myadr = new ADR.VrtDataService { Timeout = 20000 };
var token = GetTokenFromSession();
try
{
var result = myadr.getPreference(token, key);
myadr.Dispose();
return result;
}
catch (Exception ex)
{
return "Error: " + ex.Message;
}
}
public static string GetTokenFromSession()
{
var token = "";
var val2 = HttpContext.Current.Session["vDataToken"];
if (val2 != null)
{
token = (string) val2;
}
else
{
Logger.Error("Token is blank, session id is " + HttpContext.Current.Session.SessionID);
}
return token;
}
I am getting 'Token is blank' and the session id is different from the Id logged by the login method earlier.
Also, please note that the entire code is in JavaScript and aspx only serves as RESTful API with session.

It's Simple Just Use
HttpContext.Current.Session["user_id"] = ""

Related

Handling session timeout in mvc

I am trying to implement session timeout in .net core application. Redirecting to login page is working fine in non-ajax request/full postback but not in case of ajax request. The login page is displayed within the layout/current page in ajax request.
I have written a middleware which will call the controller method first in which redirection login is written.Below is my code.
Middleware
app.Use(async (ctx, next) =>
{
if (ctx.GetTenantContext<AppTenant>() == null && !ctx.Request.Path.ToString().Contains("/Home/Redirect"))
{
string redirect = "/Home/Redirect/";
if (ctx.Request.Path.ToString().Contains("Admin"))
{
redirect = "/Home/Redirect/Admin";
}
else
{
redirect = "/Home/Redirect/Trainee";
}
ctx.Response.Redirect(redirect, true);
}
else
{
await next();
}
});
Home Controller
[Route("/Home/Redirect/{AppType?}")]
public async Task<IActionResult> Redirect()
{
string appType = string.Empty;
string clientName = string.Empty;
if (!string.IsNullOrEmpty(Convert.ToString(RouteData.Values["AppType"])))
{
appType = Convert.ToString(RouteData.Values["AppType"]);
}
await _signInManager.SignOutAsync();
HttpContext.Session.Clear();
if (!string.IsNullOrEmpty(appType))
{
if (appType == "Admin")
{
if (HttpContext.Request.Cookies != null)
{
if (HttpContext.Request.Cookies["clientnamebe"] != null)
{
clientName = HttpContext.Request.Cookies["clientnamebe"].ToString();
}
}
return RedirectToRoute(new
{
controller = "Admin",
action = "Login",
clientname = clientName
});
}
else
{
if (HttpContext.Request.Cookies != null)
{
if (HttpContext.Request.Cookies["clientnamefe"] != null)
{
clientName = HttpContext.Request.Cookies["clientnamefe"].ToString();
}
}
return RedirectToRoute(new
{
controller = "Account",
action = "Login",
clientname = clientName
});
}
}
return View();
}
and in Login method I am just returning a view
[Route("Account/Login/{clientname}", Name = ApplicationType.FRONTEND)]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true, Duration = 0)]
public async Task<IActionResult> TraineeLogin(string returnUrl)
{
Return View();
}
My ajax request, though I am just loading related action results in div on tab click.
$('#tabstrip a').click(function (e) {
e.preventDefault();
var tabID = $(this).attr("href").substr(1);
localStorage.setItem("ClientCourseTab", '#'+tabID);
$("#" + tabID).html("");
var link = '#Url.Action("-1", "Course")';
link = link.replace("-1", tabID);
$("#" + tabID).load(link); // here actual request made
var appendValue = tabID.replace('_FrontEnd', '');
var appendValue = appendValue.replace('_', '');
window.location.hash = appendValue;
$(this).tab('show');
});
Any help on this appreciated !
The server does return the Redirect response in this case for the ajax request but the user doesn't get redirected to the Login page. Why? The reason is that the HTTP redirect is implicitly processed by the browser and actually never arrives to the ajax success callback. The browser processes the redirect and delivers a 200 code with the content of the redirect's destination (the login page in your case).
This is not as simple as it sounds, there are few workarounds but all of those quite complicate things. Here is one solution that you might try to implement:
How to manage a redirect request after a jQuery Ajax call
Another solution can be to have some javascript code running at a specific interval on each page to check whether the session has expired (by querying the server which complicates things even more). Whenever this javascript code detects that the session has expired, user should be immediately taken to the login page instead of waiting for an ajax request to be triggered.
The problem with querying the server would be that if you have some kind of sliding expiration of auth ticket on the server, the ticket might get renewed and session might never expire.

Session Available or not

I am calling a void function using jquery ajax in mvc3. In that function when the Session is out then also it will come to success function of ajax. I need to know whether the Session is available or not before sending the request or inside the success function of ajax.
controller Action:
protected override void Save(Query query, string queryTitle)
{
}
Why not catch the expiry of the session on the server, return an HTTP 401 Unauthorized, then check for this response in jquery and pop up a "Your session has expired, please log in again" page?
Detecting Session expiry on ASP.NET MVC
How to set HTTP status code from ASP.NET MVC 3?
How do I get the HTTP status code with jQuery?
The code you need on the initial server call is:
protected void Save(Query query, string queryTitle)
{
// would probably be better to refactor this bit out into its own method
string sCookieHeader = Request.Headers["Cookie"];
if (Context.Session != null
&& Context.Session.IsNewSession
&& sCookieHeader != null
&& sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0)
{
// session has expired
if (Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
Response.StatusCode = 401
}
else
{
// we're authenticated, so do the save
}
}
and on the client:
$.ajax(serverUrl, {
data: dataToSave,
statusCode: {
200: function(response) {
// all good, continue
401: function (response) {
// session expired!
// show login box
// make ajax call to reauthenticate
// call save method again
},
});
Your reauthentication call would look something like this:
public ActionResult Reauthenticate(username, password)
{
if (IsValidUser(username, password))
{
// sometimes used to persist user roles
string userData = string.Join("|",GetCustomUserRoles());
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
username, // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
isPersistent, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
// Encrypt the ticket using the machine key
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
// Add the cookie to the request to save it
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
Response.Cookies.Add(cookie);
}
}
(Adapted from How to set HTTP status code from ASP.NET MVC 3?)
Why not try this ?
public void Save()
{
if (Session.IsNewSession)
{
throw new Exception("This session was just created.");
}
//Go on with save matter...
}
This should return a status 500 on your AJAX function and should cause the response to fall in the fail method you defined.
Another way is to setInterval() on the client that continually sends a dummy request to the server to keep the session alive, at least when the user is editing. This might be the best way to prevent them user from losing work. You could also use this to detect loss connectivity.
When the page is loaded first time , pass the current SessionId to the client side and assign to the local javascript variable.Next have a method which will return the current SessionId before making the Save method call from the AJAX , compare the local variable against the Session Id you have received.
public string GetCurrentSessionId(){
return HttpContext.Current.Session.SessionId;
}
Javascript function
$.ajax({
Url : 'GetCurrentSessionId',
success : function(result){
if(result === "LOCALSESSIONID")
CALL THE SAVE METHOD
else
alert('Session is expired');
}
});

jQuery cookie plugin creates same cookie instead of read it

I've this:
// connect to MemberHub
function connect() {
// get unique id cookie
var uid = $.cookie('UniqueID', { path: '/' });
member.server.connect(uid).done(function (result) {
if (result.msg == 'success') {
// notify user
$('#log').append($('<li>', { html: 'Connected to MemberHUB' }));
}
});
}
Each time I try to read cookie it creates same cookie instead of read it.
Update: Here is how I assign cookies:
public static HttpCookie Create(string name, string value,
DateTime expires, HttpContextBase httpContext)
{
var cookie = new HttpCookie(name)
{
Value = value,
Expires = expires,
Secure = true,
HttpOnly = false,
Path = "/"
};
httpContext.Response.Cookies.Add(cookie);
return cookie;
}
Any advice will be helpful.
$.cookie is only read access if no other parameters (but the cookie's name) are supplied to the method [See the source]
If you're interested in reading it, just supply $.cookie('UniqueID') and remove the second parameter.
As an FYI, path (and other cookie properties) are only relevant when assigning a value, not retrieving. In other words, you don't need to supply path:'/' to get cookies that are applied to that path, document.cookie should natively perform that check.

How to include a basic http authentication in MVC4

I am using the new oAuthWebSecurity functionality in MVC4 to do Facebook authentication for users of the site and it works great.
However, what I would like to do is that for a specific controller only, enable basic HTTP authentication.
I have tried implementing a custom action filter (authenticationFilter) to intercept the call and do the basic authentication with custom code but the code never hits the overloads of the AuthorizationFilter.
Is there an easier way to implement this rather than creating a custom SimpleMembershipProvider?
You can use [Authorize] filter is as below.
public class BooksController : ApiController
{
[Authorize]
public IEnumerable<Book> Get()
{
var result = new List<Book>()
{
new Book()
{
Author = "John Fowles",
Title = "The Magus",
Description = "A major work of mounting tensions " +
"in which the human mind is the guinea-pig."
},
new Book()
{
Author = "Stanislaw Ulam",
Title = "Adventures of a Mathematician",
Description = "The autobiography of mathematician Stanislaw Ulam, " +
"one of the great scientific minds of the twentieth century."
}
};
return result;
}
}
For more information check Basic HTTP authentication
I hope this will help to you.
You can create custom AuthorizeAttribute to handle both authentication and authorization using basic authentication. This attribute works as a filter and will process the request before it gets to your controller action or Web API method. In the overridden OnAuthorize method you can grab the header information to perform authentication.
If you are using ajax to make request to a controller or Web API method use basic authentication to pass the credentials for authorization. This puts the credentials in the header. To do this is pretty straight forward by using the beforeSend event handler of the JQuery ajax function. Use jquery.base64.js to encode the information being sent over. Here is an example of how to do this.
getAuthorizationHeader = function (username, password) {
var authType;
var up = $.base64.encode(username + ":" + password);
authType = "Basic " + up;
};
return authType;
};
$.ajax({
url: _url,
data: _data,
type: _type,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", getAuthorizationHeader(username, password));
},
success: ajaxSuccessHandler,
error: ajaxErrHandler
});
This encodes the username/password that is sent in the header. Note that this is not enough security to rely on just the encoding as it is easy to decode. You still want to use HTTPS/SSL to make sure the information sent over the wire is secure.
On the server side you can make a custom AuthorizeAttribute that gets the credentials from the header, decodes them, and performs your authentication/authorization process. Note that there is a a a separate AuthorizeAttribute used by the Web API as opposed to the controller. Be sure to use System.Web.Http.AuthorizeAttribute as your base class when creating your custom AuthorizeAttribute if you are using Web API. They have different behaviors. The one for the controller will want to redirect to the logon page whereas the one for the Web API returns an HTTP code indicating success or failure. I return an HTTP code of Forbidden if authorization fails to distinguish a failure due to authorization as opposed to authentication so the client can react accordingly.
Here is an example method for getting the credentials from the header that can be used in the custom AuthorizeAttribute.
private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
{
bool gotIt = false;
username = string.Empty;
password = string.Empty;
IEnumerable<string> headerVals;
if (actionContext.Request.Headers.TryGetValues("Authorization", out headerVals))
{
try
{
string authHeader = headerVals.FirstOrDefault();
char[] delims = { ' ' };
string[] authHeaderTokens = authHeader.Split(new char[] { ' ' });
if (authHeaderTokens[0].Contains("Basic"))
{
string decodedStr = SecurityHelper.DecodeFrom64(authHeaderTokens[1]);
string[] unpw = decodedStr.Split(new char[] { ':' });
username = unpw[0];
password = unpw[1];
}
gotIt = true;
}
catch { gotIt = false; }
}
return gotIt;
}
And here is the code for decoding the header data that is used in this method.
public static string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.Encoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
Once you have the username and password you can perform your authentication and authorization using the SimpleMembership provider.

Facebook .NET SDK: How to authenticate with ASP.NET MVC 2

I am trying to get the grip on the Facebook SDK and at the same time transitioning from ASP.NET forms to MVC (finally). So please bear with me ..
I have created two controller actions:
FBLogon is execetued when the user clicks on the FB login button on the form.
He is then redirected to the FB login page.
Afterwards he gets sent back to the FBAuthorize page, which is supposed to parse the returned url for the access token. I get something like:
http://localhost:5000/account/FBAuthorize#access_token=199143326771791|827213759889396d5408fee6-100001815992604|BmYchAOMqSoZ2L0TYgCrtpoKP3M&expires_in=0
The problem I see, is that as the access_token is passed behind a #, asp.net cannot parse it on the server. Am I doing something fundamentaly wrong?
Code follows:
public ActionResult FBLogon()
{
var settings = ConfigurationManager.GetSection("facebookSettings");
IFacebookApplication current = null;
if (settings != null)
{
current = settings as IFacebookApplication;
if (current.AppId == "{app id}" || current.AppSecret == "{app secret}")
{
return View();
}
}
string[] extendedPermissions = new[] { "publish_stream", "offline_access" };
var oauth = new FacebookOAuthClient { ClientId = current.AppId, RedirectUri = new Uri("http://localhost:5000/account/FBAuthorize") };
var parameters = new Dictionary<string, object>
{
{ "response_type", "token" },
{ "display", "page" }
};
if (extendedPermissions != null && extendedPermissions.Length > 0)
{
var scope = new StringBuilder();
scope.Append(string.Join(",", extendedPermissions));
parameters["scope"] = scope.ToString();
}
var loginUrl = oauth.GetLoginUrl(parameters);
return Redirect(loginUrl.ToString());
}
public ActionResult FBAuthorize()
{
FacebookOAuthResult result;
if (FacebookOAuthResult.TryParse(Request.Url, out result))
{
if (result.IsSuccess)
{
var accesstoken = result.AccessToken;
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
Ok. The facebook docs say it quite clearly:
Because the access token is passed in
an URI fragment, only client-side code
(such as JavaScript executing in the
browser or desktop code hosting a web
control) can retrieve the token. App
authentication is handled by verifying
that the redirect_uri is in the same
domain as the Site URL configured in
the Developer App
from http://developers.facebook.com/docs/authentication/ ---> Client-side Flow Section.
So I'm sending the token back to my server to complete the authentication..
Update:
The sending back to the server I do using Javascript something like this:
var appId = "<%: Facebook.FacebookContext.Current.AppId %>";
if (window.location.hash.length > 0) {
accessToken = window.location.hash.substring(1);
var url = window.location.href.replace(/#/, '?');
window.location = url;
}
On the server then I have the following action. Not very nice but it works..
public ActionResult FBAuthorize()
{
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
/// hack to make FacebookOuthResult accept the token..
url = url.Replace("FBAuthorize?", "FBAuthorize#");
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string[] extendedPermissions = new[] { "user_about_me", "offline_access" };
var fb = new FacebookClient(result.AccessToken);
dynamic resultGet = fb.Get("/me");
var name = resultGet.name;
RegisterModel rm = new Models.RegisterModel();
rm.UserName = name;
rm.Password = "something";
rm.Email = "somethig";
rm.ConfirmPassword = "23213";
//Label1.Text = name;
//Response.Write(name);
//return RedirectToAction("register", "Account", rm);
ViewData["Register"] = rm;
return RedirectToAction("Register");
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
I found this post http://facebooksdk.codeplex.com/discussions/244568 on codeplex. I think this is what you need.
Note that instead of using the client-side flow, you need to use the server-side flow.
This is what you should do
Create a login link for server-side flow. After Authorization, facebook will return an url containing a code instead of a access token.
Then you request for a token from facebook using the code. this is my example
public ActionResult FBAuthorize()
{
FacebookOAuthClient cl = new FacebookOAuthClient(FacebookContext.Current);
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
// verify that there is a code in the url
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string code = result.Code;
// this line is necessary till they fix a bug *see details below
cl.RedirectUri = new UriBuilder("http://localhost:5000/account/FBAuthorize").Uri;
var parameters = new Dictionary<string, object>();
//parameters.Add("permissions", "offline_access");
Dictionary<String, Object> dict = (Dictionary<String, Object>)cl.ExchangeCodeForAccessToken(code, new Dictionary<string, object> { { "redirect_uri", "http://localhost:5000/account/FBAuthorize" } });
Object Token = dict.Values.ElementAt(0);
TempData["accessToken"] = Token.ToString();
return RedirectToAction ("ShowUser");
}
else
{
var errorDescription = result.ErrorDescription;
}
}
else
{
// TODO: handle error
}
return View();
}
*There is bug when using IIS in localhost, see the original post for details (the redirect uri when asking for the token must be the same as the one used asking for the code)
It is highly recommended to use IIS and not visual studio web server. There are many things that wont work in visual studio web server.
I am in the same spot you are at the moment.
We never get the Request.QueryString populated becasue of the "fragment" or # in the url.
Love to know if you solved this and how.
It does not look like the FacebookOAuthResult class was written to be used in web applications of any sort.
you can change the response type in you scope paramas to be "code" then it will send back a code in the querystring in which you can swap for a token.

Categories

Resources