I am trying to implement a Web Application Project where my web pages can check the server for the Authentication ticket expiration date/time using AJAX.
I am using Forms Authentication with slidingExpiration.
The problem I run across is I can't figure out how to check the value without resetting it. I created a simple page - CheckExpiration.aspx - below is the code behind:
private class AjaxResponse
{
public bool success;
public string message;
public string expirationDateTime;
public string secondsRemaining;
public string issueDate;
}
protected void Page_Load(object sender, EventArgs e)
{
AjaxResponse ar = new AjaxResponse();
JavaScriptSerializer js = new JavaScriptSerializer();
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string expiration = id.Ticket.Expiration.ToString();
TimeSpan timeRemaining = id.Ticket.Expiration - DateTime.Now;
ar.success = true;
ar.expirationDateTime = expiration;
ar.issueDate = id.Ticket.IssueDate.ToString();
ar.secondsRemaining = timeRemaining.Minutes.ToString() + ":" + timeRemaining.Seconds.ToString();
}
else
{
ar.success = false;
ar.message = "User not authenticated";
}
string output = js.Serialize(ar);
Response.Write(js.Serialize(ar));
}
I call this page from the Master page in my application using ajax every second. Past the halfway point in the authentication expiration, the expiration gets reset.
How do I prevent this behavior? Is there anything I can do in the header of the request maybe?
Why don't you store the expiration as a session variable that you compute yourself? You only need to get the value of id.Ticket.Expiration once. Then each call, get the value from the server and increment it accordingly, and store it back on the server.
http://msdn.microsoft.com/en-us/library/ms178581%28v=vs.85%29.aspx
Pseudocode:
if(!Session.KeyExists("Expiration"))
{
Session["Expiration"] = id.Ticket.Expiration;
}
Session["TimeRemaining"] = Session["Expiration"] - DateTime.Now;
// get all ajaxy here
Put your CheckExpiration.aspx page in its own application and deploy this as a virtual directory beneath your main application. In that virtual directory, configure slidingExpiration=false. Your code will work as-is but will not regenerate the ticket when it gets below half the time until expiration.
Here's what I did in a quick local project to verify that it works:
Created a new web application AuthTest4 and configured it to use local IIS server in path /AuthTest4
Went into IIS and changed the Machine Key setting for /AuthTest4 to uncheck all the AutoGenerate/Isolate options and generated its own MachineKey.
Created an empty web application ExpCheck and put your CheckExpiration.aspx code in it
Configured ExpCheck web application to use local IIS in the virtual directory /AuthTest4/ExpCheck
Modified the web.config of ExpCheck application to have only the section shown below
ExpCheck web.config. All other security settings will cascade down from the parent virtual directory.
<system.web>
<authentication mode="Forms">
<forms slidingExpiration="false" />
</authentication>
</system.web>
Related
Hi I needed a serious help
i have tried everything but am not able to change the path of ASP.NET_sessionid cookie
its path is always set to "/" , i want to set it to a folder or directory
this issue need to be solved as it was raised by app security team
have tried , iis rewrite rule , custom session id manager
any help much aprreciated
As #iamdln said, you need to create your own SessionIDManager but you also need to config it on your Web.config.
It worked for me.
Your SessionIdManager class,
public class MySessionIDManager : SessionIDManager, ISessionIDManager
{
void ISessionIDManager.SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
{
base.SaveSessionID(context, id, out redirected, out cookieAdded);
if (cookieAdded)
{
var name = "ASP.NET_SessionId";
var cookie = context.Response.Cookies[name];
cookie.Path = "/yourPath";
}
}
}
Web.config, replace namespace and class for yours.
This goes inside <system.web> section.
<sessionState sessionIDManagerType = "Namespace.MySessionIDManager"></sessionState>
Original links:
ASP.NET Forum - Explains how to override path
StackOverFlow - Explains how to override domain
Both are quite similar anyway.
You will need to create your own SessionIDManager which is inherited from ISessionIDManager and change the cookie.Path to whatever you want.
static HttpCookie CreateSessionCookie(String id) {
HttpCookie cookie;
cookie = new HttpCookie(Config.CookieName, id);
cookie.Path = "/";
cookie.SameSite = Config.CookieSameSite;
// VSWhidbey 414687 Use HttpOnly to prevent client side script manipulation of cookie
cookie.HttpOnly = true;
return cookie;
}
I have an Asp.net web application where I am using FormsAuthentication for User login.
I want to prevent multiple logins to the same user at the same time.
For this I have set the FormsAuthentication timeout to 15 minutes and Session.timeout to 15 minutes.
When the user closes the browser without logging out, or if the user is inactive for 15 minutes, it is not firing the Session_End() event in global.asax.cs file. I want to update the database field in the Session_End() event.
Code for Login:
if (Membership.ValidateUser(username, password))
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1"));
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
context.Response.Cookies.Add(authCookie);
context.Response.Redirect("/HomePage", false);
}
Global.asax.cs:
protected void Session_Start(Object sender, EventArgs e)
{
Session["init"] = 0;
Session.Timeout = 15;
}
protected void Session_End(Object sender, EventArgs e)
{
PersonObject person = new PersonObject();
// calling the function to update entry in database
person.ResetUserLoginStatus(HttpContext.Current.User.Identity.Name);
}
Function to update entry in database:
public bool ResetUserLoginStatus( string username="")
{
string sql = "UPDATE Person SET IsLogged=0 WHERE Person = #Person";
PersonObject person = new PersonObject();
object id = person.ExecuteScalar(sql, new Dictionary<string, object>() {
{ "Person", (!string.IsNullOrEmpty(username)?username:User.Name )}
}, "Person");
return true;
}
Web.config:
<authentication mode="Forms">
<forms loginUrl="/Security/Login.ashx/Home" name="SecurityCookie" timeout="15" slidingExpiration="true">
</forms>
</authentication>
<sessionState timeout="15" mode="InProc"></sessionState>
The problem is that when the browser is closed the ResetUserLoginStatus() method isn't called and I am unable to reset my value to 0. Since the field has not been reset to 0, that user won't be able to log in again.
Please suggest.
Session_End is actually not that useful or reliable. For one thing, it only fires at the end of the pipeline processing when an HTTP request has been received and a response has been rendered. That means it does NOT fire for a user who has simply closed their browser. Also, the event will never fire except for certain types of session state-- it won't work with State Server, for example, or SQL-based session state. The bottom line is you can't rely on it to maintain an unambiguous "Is logged in" flag.
Instead, I would store a "last page request received" time stamp. You can then infer the value of a "is logged in" flag; any user who has submitted a request in the past 15 minutes is still logged in.
I have the following code which returns the reports from my SSRS server, afterwards I then store the paths to each individual list which allows users to run them from within the application. The below works fine.
NetworkCredential serviceCredentials = new NetworkCredential()
{
UserName = username,
SecurePassword = EncryptionManager.DecryptToSecureString(password),
Domain = domain
};
reports = new ObservableCollection<object>(reportsManager.FindReports(reportsWebService, reportsFolderName, serviceCredentials));
//FindReports
ReportingService2005 rs = new ReportingService2005();
rs.Url = reportsWebService;
rs.Credentials = serviceCredentials;
CatalogItem[] catalogItems = rs.ListChildren(#"/" + reportsFolderName, false);
However the problem is when a user selects a report to view it shows the following error:
The permissions granted to user are insufficient for performing this
operation.
I understand that the quick fix to this would be to add the users domain into the security section on the Report server, however this is not appropriate.
My question is I can supply credentials to allows a specified user to access the report folder is it possible to pass this along so that user can run a report?
Each of my reports use built in connection strings NOT windows authentication.
Edit: I am using Reporting WinForms.
Windows Forms
In a windows forms project you can pass a suitable System.Net.NetworkCredential to ServerReport.ReportServerCredentials.NetworkCredentials property of ReportViewer. This way, all reports will be executed using the passed credential:
reportViewer1.ServerReport.ReportServerCredentials.NetworkCredentials =
new System.Net.NetworkCredential("username", "password", "domain");
Web Forms
The solution for a Web Forms is different. In a Web Forms project, to pass a suitable credential to RePortViewer you need to implement IReportServerCredentials. Then you can assign the value to ServerReport.ReportServerCredentials property of ReportViewer control. This way, all reports will be executed using the passed credential.
Example
Here is a simple implementation. It's better to store username, password and domain name in app config and read them from config file:
using System;
using System.Net;
using System.Security.Principal;
using Microsoft.Reporting.WebForms;
[Serializable]
public sealed class MyReportServerCredentials : IReportServerCredentials
{
public WindowsIdentity ImpersonationUser { get { return null; } }
public ICredentials NetworkCredentials
{
get
{
return new NetworkCredential("username", "password", "domain");
}
}
public bool GetFormsCredentials(out Cookie authCookie, out string userName,
out string password, out string authority)
{
authCookie = null;
userName = password = authority = null;
return false;
}
}
Then in Page_Load pass the credential this way:
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
this.ReportViewer1.ServerReport.ReportServerCredentials =
new Sample.MyReportServerCredentials();
}
Note
In cases which you want to use ReportViewer with no session state you can also implement IReportServerConnection. In this case you need to add a key value in appsettings section of config file to introduce the implementation this way:
<add key="ReportViewerServerConnection" value="YourNameSpace.YourClass, YourAssemply" />
In this case, you don't need code in Page_Load and the config would be enough. For more information take a look at this great blog post by Brian Hartman.
I need to Generate a Verification Code like: 1599 for Login.
The User must input correct number when Login in.
But My default session expired time is 30 minute.
I don't want let the Verification Code expired time so long. I just need it keep only 1 minute.
So How I set expired time out only for the Verification Code session variable?
Update1:
I don't want to change the global session expired time. I just need
set only for the Verification Code session variable before login.
There's no built-in way, BUT you can achieve it very simple:
Session[name] = value;
//now add a cleanup task that will run after the specified interval
Task.Delay(expireAfter).ContinueWith((task) => {
Session.Remove(name);
});
I wrapped this into a very handy "session extender" class source code here you can use it like this:
//store and expire after 5 minutes
Session.AddWithTimeout("key", "value", TimeSpan.FromMinutes(5));
PS. If you're still using an old .NET version (no async code, no tasks) you'll find a compatible version in my blogpost as well.
Sorry guys, I finally use this solution:
when generate the Verification Code I will save it to session
Session["VerificationCode"] = code;
That's everybody knows. but how about the time? I change it :
Session["VerificationCode"] = code;
Session["VerificationTime"] = DateTime.Now;
Now the time save in session too. Then I can find it when User Login:
// Verification Code Expired
if ((DateTime)Session["VerificationTime"]).AddMinutes(1) < DateTime.Now)
{
ModelState.AddModelError("VerificationCode", "Verification Code Expired!");
return View(adminLogin);
}
// Verification Code Error
if (Session["VerificationCode"].ToString().ToUpper() != adminLogin.VerificationCode.ToUpper())
{
ModelState.AddModelError("VerificationCode", "Verification Code Error");
return View(adminLogin);
}
You should create a class or structure that is stored in SessionState rather than just storing the verification code itself. This will allow you to easily check the code and the expiration date of the code.
E.g.:
public class VerificationCode {
public string Code { get; set; }
public DateTime ExpirationDate { get; set; }
}
// in your method
VerificationCode newCode = new VerificationCode {
Code="1559",
ExpirationDate = System.DateTime.Now.AddMinutes(1)
};
Session["VerificationCode"] = newCode;
// in the method where you check the expiration date and code...
VerificationCode code = (VerificationCode)Session["VerificationCode"];
// you can remove the verification code after pulling it out, as you only need
// it once, regardless of whether it is still good or expired.
Session.Remove("VerificationCode");
if (code.ExpirationDate < System.DateTime.Now){
// the verification code expired...
// can remove the verification code after successful login
Session.Remove("VerificationCode");
} else {
// verification code is still good.
// Log the user in?
// can remove the verification code after successful login
Session.Remove("VerificationCode");
}
Updated
- Added removal of verification code from session when expired OR upon successful login.
- square bracket accessor for Session variables. Previously used VB (doh!) syntax (parentheses)
You can't change session time for one particular case. Instead you can use Cache.Add method with absoluteExpiration parameter to store VerificationCode in the Cache.
MemoryCache.Default.Add([UserIdentity], [Verification Code],
new DateTimeOffset().ToOffset(TimeSpan.FromMinutes(1)));
And then get it from Cache
var verificationCode = MemoryCache.Default.Remove([UserIdentity]);
SESSION STATE SETTINGS
By default ASP.NET uses cookies to identify which requests belong to a particular session. If cookies are not available, a session can be tracked by adding a session identifier to the URL. To disable cookies, set sessionState cookieless="true". But this will change the setting for the all the sessions available for the application.
<configuration>
<system.web>
<sessionState mode="StateServer" cookieless="false" timeout="120"/>
</system.web>
</configuration>
References : http://msdn.microsoft.com/en-us/library/ms525473%28v=vs.90%29.aspx
In your case you can use cookie to track and set it to expire after 1 Minute , I can give you an example like:
Response.Cookies["userName"].Value = "patrick";
Response.Cookies["userName"].Expires =DateTime.Now.AddMinutes(1);
HttpCookie aCookie = new HttpCookie("lastVisit");
aCookie.Value = DateTime.Now.ToString();
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
I have some proof concept code for a HTTP module. The code checks to see if a cookie exists, if so it retrieves a value, if the cookie does not exist it creates it and sets the value.
Once this is done I write to the screen to see what action has been taken (all nice and simple). So on the first request the cookie is created; subsequent requests retrieve the value from the cookie.
When I test this in a normal asp.net web site everything works correctly – yay! However as soon as I transfer it to SharePoint something weird happens, the cookie is never saved - that is the code always branches into creating the cookie and never takes the branch to retrieve the value - regardless of page refreshes or secondary requests.
Heres the code...
public class SwithcMasterPage : IHttpModule
{
public void Dispose()
{
throw new NotImplementedException();
}
public void Init(HttpApplication context)
{
// register handler
context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute);
}
void PreRequestHandlerExecute(object sender, EventArgs e)
{
string outputText = string.Empty;
HttpCookie cookie = null;
string cookieName = "MPSetting";
cookie = HttpContext.Current.Request.Cookies[cookieName];
if (cookie == null)
{
// cookie doesn't exist, create
HttpCookie ck = new HttpCookie(cookieName);
ck.Value = GetCorrectMasterPage();
ck.Expires = DateTime.Now.AddMinutes(5);
HttpContext.Current.Response.Cookies.Add(ck);
outputText = "storing master page setting in cookie.";
}
else
{
// get the master page from cookie
outputText = "retrieving master page setting from cookie.";
}
HttpContext.Current.Response.Write(outputText + "<br/>");
}
private string GetCorrectMasterPage()
{
// logic goes here to get the correct master page
return "/_catalogs/masterpage/BlackBand.master";
}
This turned out to be the authentication of the web app. To work correctly you must use a FQDM that has been configured for Forms Authentication.
You can use Fiddler or FireBug (on FireFox) to inspect response to see if your cookie is being sent. If not then perhaps you can try your logic in PostRequestHandlerExecute. This is assuming that Sharepoint or some other piece of code is tinkering with response cookies. This way, you can be the last one adding the cookie.