I have an ASP.NET MVC application that consumes various operations of a Web API. It uses ACS for security and so users have to log on first with their Microsoft account before they can do anything.
One of these web API operations is getting the list of permissions for the currently logged on user. This call is done for every page request, as we need this information to correctly display, disable or hide UI elements. This works fine.
As permissions don't change often, I would like to cache them so that the call to the web API is only done the first time.
Normally session is the way to keep user-specific data in memory, but I want to remain stateless/sessionless.
Would it be technically OK to use the application cache, in which I store the permissions with a key that includes the user's unique identification? Are there any risks/disadvantages of doing it like this?
[I also would like to keep the option open to later replace it with (Azure) distributed caching later, if needed, but for now the solution should be a simple built in one which is free :)]
EDIT: the cache is meant to live as long as the user is working, so it's mostly short-term cache.
Application cache seems not to be not a good option. First of all, your application process may be restarted and then all the data will be lost. On other hand, if the application is running for a long time and you have a significant number of users, it will cause significant growth process of memory size.
I'd suggest you to use encrypted cookie. Upon successful login you set the cookie with his id / permission and upon logout remove it. This way you make user login really persistent and independent on session / server state and also free your server from unnecessary storage. Encryption protects against the possibility to abuse the cookie by its reverse engineering and receive another user's permissions.
See the sample code below:
// on login successful
string EncryptedUserId = EncriptCookie(UserId, Permissions);
HttpCookie LoginCookie = new HttpCookie("yoursitename");
LoginCookie.Values.Add("userinfo", EncryptedUserId);
LoginCookie.Expires = DateTime.Now.AddYears(10);
HttpContext.Current.Response.AppendCookie(LoginCookie);
public static void Logout()
{
HttpCookie LoginCookie = new HttpCookie("yoursitename");
LoginCookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.AppendCookie(LoginCookie);
}
private static string EncriptCookie(int UserId, string Permissions)
{
string CookieString = UserId.ToString() + "#" + Permissions);
DESCryptoServiceProvider Crypt = new DESCryptoServiceProvider();
Crypt.Key = ASCIIEncoding.ASCII.GetBytes("MYSECRET");
Crypt.IV = ASCIIEncoding.ASCII.GetBytes("MYSECRET");
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, Crypt.CreateEncryptor(), CryptoStreamMode.Write);
byte[] EncBytes = ASCIIEncoding.ASCII.GetBytes(CookieString);
cs.Write(EncBytes, 0, EncBytes.Length);
cs.FlushFinalBlock();
string EncryptedCookie = Convert.ToBase64String(ms.ToArray());
return EncryptedCookie;
}
Related
I have an application(core 2.2.0) which uses a cookie for authentication, below is the code from the Startup.cs file
services.AddAuthentication(cookieConfig.AuthScheme)
.AddCookie(cookieConfig.AuthScheme, options => {
options.LoginPath = new PathString(cookieConfig.LoginPath);
options.AccessDeniedPath = new PathString(cookieConfig.AccessDeniedPath);
options.Cookie = cookie;
options.Events = cookieEvents;
});
And below code is from the Sign-in API
await HttpContext.SignInAsync(_cookieConfig.AuthScheme, userPrincipal, authProps);
Suppose this application generates a cookie 'ABC', and it has a URL1 - https://somedomain.com/api
and I have another API hosted at URL2 - https://somedomain.com/another_api/whatever
But remember both APIs are a different project.
When I pass this cookie to URL1, in the OnAuthorization() I can see the Identities and the Claims properties with correct values of that user and it authorizes the user perfectly. But when I pass the same cookie to URL2 all claims and identity properties show null and it does not authorize.
My question: Is it possible to share authentication between different domains? If yes, then how? If not possible then please suggest an alternative approach.
Additional Details -
We have the above code which generates (at App1) a cookie ABC=some_encrpyted_value.
And in App2 I am trying to unprotect that cookie in below manner
string cookieValue = context.HttpContext.Request.Cookies["ABC"];
var provider = DataProtectionProvider.Create(new DirectoryInfo(#"C:\temp-keys\"));
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "ABC", "v2");
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);
I have placed the data protection key from App1 in the folder 'C:\temp-keys'.
I found this code somewhere in StackOverflow itself and it throws 'CryptographicException: The payload was invalid.' exception at line
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
My understanding of the App1 code is that the Identity and Claim values (with some other values) are encrypted and cookie ABC is generated, and when we send a request this cookie is decrypted and the Identity and Claims are get populated in the context.HttpContext.User
I actually wanted it to work the same way in the App2, I want to pass the cookie and the identity values should be populated in the context.
It is possible. To deal with this type of scenarios we have been using ASP.NET machineKey. That is does is it encrypts/descriptor the auth data using the same key so that different sites can share the same authenticated sessions and data.
In Dotnet Core this machineKey concept has evolved and now called - new data protection system. Microsoft.AspNetCore.DataProtection.SystemWeb package is used to implement the data protection system. To read more on this.
There are various ways how you can store and use the key:
ProtectKeysWithAzureKeyVault (if you are using Azure)
PersistKeysToFileSystem (this is the easier one)
ProtectKeysWith*
UnprotectKeysWithAnyCertificate
Details of the individual scenario is described here.
I work on an MVC Web Application using Azure AD with OAuth 2 and Open ID Connect for Authorization of users.
Per documentation tokens are refreshed automatically when a token expires after 60 minutes (which is fine).
Now the problem is, to acquire a token I need to know the currently authenticated user which is stored in a cookie. The code to acquire a Token is like this:
public async Task<AuthenticationToken> GetTokenForApplication(string resourceID)
{
string signedInUserID = ClaimsPrincipal.Current.SignedinUserId();
var tenantID = ClaimsPrincipal.Current.TenantId();
string userObjectID = ClaimsPrincipal.Current.SignedinUserObjectId();
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(Config.ClientId, Config.AppKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(string.Format("{0}{1}", Config.AadInstance, tenantID), new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(resourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
var token = new AuthenticationToken(authenticationResult.AccessToken) { ExpiresOn = authenticationResult.ExpiresOn };
return token;
}
Now I am in the dilemma, that the ClaimsPrincipal.Current.SignedinUserId() method call throws a null reference exception. When I inspect the ClaimsPrincipal.Current object, no data about the logged in user is available. But this is the Information needed to renew / request a token.
What is the best practice in an MVC Web App? Is there a way to extend the validity of the cookie or is there any way to reauthenticate the current user without redirecting to the root page of the web application?
After doing more research I have found these two pages which describe some options to deal with my problem pretty good:
Controlling a Web App’s session duration
and ASP.NET-Identity-Cookie-Authentication-Timeouts
are these good approaches?
After doing more research I have found these two pages which describe some options to deal with my problem pretty good:
Controlling a Web App’s session duration
and ASP.NET-Identity-Cookie-Authentication-Timeouts
are these good approaches?
I've been reading a lot about session fixation attacks and the most popular solutions I've come across are changing the SessionID when user logs in and creating an additional cookie using a GUID to verify the user "belongs" to the SessionID.
My question is this: Isn't it enough to just delete the SessionID cookie (ASP.NET_SessionID) to ensure a new SessionID is generated?
In MVC 5, when the user logs in an additional encrypted user claims cookies is created (AspNet.ApplicationCookie) which Identity uses to authenticate the user upon each request. The additional "GUID cookie" seems unnecessary.
I’m originally a .NET desktop application developer writing my first MVC app and the learning curve has been a bit steep… although refreshingly enjoyable.
Thanks for any help.
Let me try to explain the issue and the solution by using comparisons between desktop and web apps (both in .Net)
When you start your desktop app, the first thing the app shows is a login screen, after which your access to the UI is granted. Now, each time the app's exe is started, it writes the "RunID" to a text file and shows the login screen. The RunID is how the rest of your usage of the app is going to be tracked/correlated.
Assume for a second that the file was on C:\RunID.txt.
An attacker (hacker) can start the exe (without logging in) on Machine1 and copy the contents of C:\RunID.txt to Machine2. Now as soon as you log in on Machine1, the RunID token from Machine1 will also work on Machine2, this is called session fixation.
The ideal way to fix it is to ABANDON the pre-authentication token, and issue a NEW Post-Authentication token. So, you would get a new Token after authentication (or in your case, an additional GUID) which will NOT EXIST on Machine2 and hence provide a level of security in addition to the RunID random token (Session ID)
Let me know if you'd like further explaination, but that is why even in MVC, you should abandon the previous session and create a new session post-auth to avoid session fixation, as a compensating control, you can add a GUID cookie too correspond with the Session ID cookie.
You can do this to avoid that situation:
SessionIDManager Manager = new SessionIDManager();
string NewID = Manager.CreateSessionID(Context);
string OldID = Context.Session.SessionID;
bool redirected = false;
bool IsAdded = false;
Manager.SaveSessionID(Context, NewID, out redirected, out IsAdded);
Response.Write("Old SessionId Is : " + OldID);
if (IsAdded)
{
Response.Write("<br/> New Session ID Is : " + NewID);
}
else
{
Response.Write("<br/> Session Id did not saved : ");
}
Support link:
Link
I had a Posting on a blog about Sessions AND Cookies. Here are details
Sessions
Sessions are More Secure
Sessions are on the server
Cookies
Cookies are On client side
Less Secure
Once it is disable on browser the difficult to use.
On the basis of above argument i used sessions in Login system to keep UserId,UserName & roleName
Now on the the basis of roleName i will decide either this is Admin to enter to administrator section or not.
I have used this Code in Model in MVC
public bool LoginMe()
{
Int64 Error;
//create db
Database db = DatabaseFactory.CreateDatabase("DBContext");
DbCommand dbCommand = db.GetStoredProcCommand("ValidateUser");
db.AddInParameter(dbCommand, "#Username", DbType.String, this.UserName);
db.AddInParameter(dbCommand, "#Password", DbType.String, EncryptPassword(this.Password));
db.AddOutParameter(dbCommand, "#Error", DbType.Int64, 10);
DataSet dsResult = db.ExecuteDataSet(dbCommand);
Error = Convert.ToInt64(db.GetParameterValue(dbCommand, "#Error"));
if (Error == 1100)
{
try
{
var query = (from o in dsResult.Tables[0].AsEnumerable()
select new AllUser
{
UserId = o.Field<int>("UserId"),
UserName = o.Field<string>("UserName"),
roleName = o.Field<string>("roleName"),
}).Single(); // this will raise an exception if there isn't just one record returned
Session["UserId"] = query.UserId;
Session["UserName"] = query.UserName;
Session["roleName"] = query.roleName;
return true;
}
catch {
// do nothing and let method return false as something has gone wrong.
// add logging here if you are using it to show there has been a problem
}
}
return false;
}
I used it in View like #Session["UserId"]
Now an expert comment on this like
If you aren't using https and securing the session cookie then this might make it easy to hack your site, although that's the same for any session based site (nearly all of them)
It might be nice to add some check so that if you remove a user's rights, the session variables are deleted the next time that user requests something from the server,
otherwise they could carry on using the site even though their account it banned.You'd have to decide if this is likely and then how you want to do this (using an authorization filter maybe.)
Above comments confused me.Can any body make it clear?What is the best way to keep these information?
Session state uses client tickets to identify the server-side session, it may be susceptible to session ID spoofing and injection attacks.
So, to hack session values one would require hacking the remote-server.
And yes, for highly secure application(such as online banking) use https.
http://msdn.microsoft.com/en-us/magazine/cc163730.aspx#S9
Secure sockets layer (SSL) should be used to prevent network-level sniffing of session IDs, authentication tickets, application cookies, and other request/response information.
Can session value be hacked?
Use HTTPS if you application handles sensitive information(credit-card number,account num,passwords).
Store the User object (model with userId,username,role) in the session than separate attributes
Set setHttpOnly attribute for SESSION_ID.
It might be costly to refresh the User object stored in session before invoking every operation to reflect the current rights stored in database.
i want to add 3 serial key to client system using httpcookie when he visited my website
my website off course in asp.net MVC
but
serial key is different different not same.
when i add a 4th cookie then 1 key is automatically deleted.
how i can do this.
when user want to see then he can see recent 3 key.
Are you know how to add this cookie to client system by asp.net mvc website.
how i can add key1 , key 2 , key3 to client system.
Here's how you can do that.
Writing the serial-keys.
//create a cookie
HttpCookie SerialKeys = new HttpCookie("SerialKeys");
//Add serial-key-values in the cookie
SerialKeys.Values.Add("key1", "your-first-serialkey");
SerialKeys.Values.Add("key2", "your-second-serialkey");
SerialKeys.Values.Add("key3", "your-third-serialkey");
SerialKeys.Values.Add("key4", "your-fourth-serialkey");
//set cookie expiry date-time. Made it to last for next 12 hours.
SerialKeys.Expires = DateTime.Now.AddHours(12);
//Most important, write the cookie to client.
Response.Cookies.Add(SerialKeys);
Reading the serial-key cookie.
//Assuming user comes back after several hours. several < 12.
//Read the cookie from Request.
HttpCookie SerialKeys = Request.Cookies["SerialKeys"];
if (SerialKeys == null)
{
//No cookie found or cookie expired.
//Handle the situation here, Redirect the user or simply return;
}
//ok - cookie is found.
//Gracefully check if the cookie has the key-value as expected.
if (!string.IsNullOrEmpty(SerialKeys.Values["key1"]))
{
string serialKey = SerialKeys.Values["key1"].ToString();
//Yes key1 is found. Mission accomplished.
}
if (!string.IsNullOrEmpty(SerialKeys.Values["key2"]))
{
string serialKey = SerialKeys.Values["key2"].ToString();
//Yes key2 is found. Mission accomplished.
}
You can add cookies to the user when a user starts a session. This can be done by handling the session start even in the global.asax file of your application the adding in the logic code for creating these keys.
void Session_OnStart() {
HttpCookie key1 = new HttpCookie("key1","123");
Request.Cookies.Add(key1);
}
Then for the 4th cookie thing your were talking about you can create your code logic by referencing the users cookies by using Response.Cookies["cookie"] or remove one using Request.Cookies.Remove("cookie");
All browser limit in one way or another the amount of cookies a certain website can store. If for example your browser accepts n cookies, the when you send the n+1 the browser will delete your oldest one. It seems to me that this is what is going on.
A possible solution is to use one cookie with various subvalues instead of a single one. That way you can have in one cookie as many values as you want (subjected always to the cookie maximum size limit which is 4086 bytes).
The code to do that would be something like:
//This code creates the cookie, ads some subvalues to it and then adds it to the request so its sent to the browser
HttpCookie cookie = new HttpCookie();
cookie.Name = "MyKeys";
cookie.Values.Add("Key1", "ValueKey1");
cookie.Values.Add("Key2", "ValueKey2");
cookie.Values.Add("Key3", "ValueKey3");
//etc...
Request.Cookies.Add(cookie);
//This code reads the data from the cookie.
HttpCookie rcookie = Response.Cookies.Get("MyKeys");
string value1, value2, value3;
//We verify it the variable is null because the cookie might not exist in the users browser. If so, this variable would be null
if (rcookie != null)
{
value1 = rcookie.Values.Get("Key1");
value2 = rcookie.Values.Get("Key2");
value3 = rcookie.Values.Get("Key3");
}