CookieTempDataProvider causes CryptographicException - c#

I have an ASP.NET Core app (1.1 targeting full framework) that uses TempDataProvider to store some metadata about the currently logged-in user, such as their "display name" and some other preferences for the app. In my Startup.cs ConfigureServices method I have added the line
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
which is populated after login using (for example)
TempData["DisplayName"] = login.DisplayName;
This works well, creating an encrypted, chunked session cookie for the user named .AspNetCore.Mvc.CookieTempDataProvider. I can "peek" and clear on logout, etc. as I expect. However after some interval - perhaps the app pool going idle, but the browser session remaining active - I receive a CryptographicException:
System.Security.Cryptography.CryptographicException: The key {guid} was not found in the key ring
It appears that the browser session cookie is still good, but the server has lost its ability to decrypt and use it. Currently the only way to resolve is to manually clear the cookie and let the app create a new one.
Is there any way to protect against this behavior? I do want the contents of the cookie to be difficult/impossible to forge, and this seemed like a valid mechanism to achieve that.

Related

Are the data protection keys necessary for docker container?

I am getting the following error in my logs when running my application on a docker container.
[08:20:54 WRN] Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. <s:Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository>
[08:20:54 WRN] No XML encryptor configured. Key {<some-id} may be persisted to storage in unencrypted form. <s:Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager>
I was reading into data protection keys, especially from this article https://www.edument.se/post/storing-the-asp-net-core-data-protection-key-ring-in-azure-key-vault?lang=en and it seems to be something that might be really important when deploying an app. However, what I don't understand is what is it being used for? I am not using identity or session cookies. And for the technologies I am using, I create my own keys to encrypt the information.(For example for JWT or for encrypting some text).
I do use cookies to set my jwt token by using the set-token header with HTTPonly flag. Could that be what the key is being created for?
I want to know in order to define if we should take action to make the keys persistent or if can just ignore it. I would appreciate it a lot if someone has some insight into this that is willing to share.
Here a screenshot of the file where the keys are being stored
Actually, the section What happens if I don’t configure the data protection service in ASP.NET Core? of the referenced post gives a great explanation of what it is used for.
And yes, setting HttpOnly=true means encrypting the cookie's value with the Key Ring. You can do a simple test: run your service locally in a docker container, perform the flow that sets the cookie on your browser, then remove the container and create a new one. Now try to perform the action that requires the cookie, and it will fail because your service can't longer decrypt the cookie's value.

ASP.NET Core. How can I invalidate JWT-Token after password change

Sorry for my bad English. I'm writing an application in ASP.NET Core using Vue.JS for client-side. For authenticate user I'm using JWT and ASP.NET Identity. I have a method for change the password. But I can't understand: How to invalide token after password change? I want that the user authenticated in another browser will logout after that. Is there a man who haved a problem like this?
You normally don't invalidate JWT's because they are meant to be short-lived access tokens and therefore after the password change, request for new token will prompt the user to reenter credentials.
If you do absolutely need to invalidate the JWT immediatelly after password change - you need to look into Introspection where your backend api essentially has a backchannel to your token issuer and it can then re-validate token every request. This way if you invalidate token at the issuer side - it will reflect on the api side immediately.
I've been thinking about this and the inability to invalidate a JWT that's already out there may not be built into anything, but is possible.
Here's the narrative: You have an alarm system installed that can be controlled via web and your ex-S/O is logged in to your previously shared account. They are upset and they keep enabling the alarm at random times.
If the web app uses JWTs to store session, you could change your password but the JWT your ex possesses will still be usable for a period of time until the timeout is reached.
Solution 1: short timeout. but what if you want to stay logged in for longer periods (such as a password manager)
Solution 2: logout ALL users by changing the Signing Key of your Certified Authority, basically invalidating ALL JWTs across the board. This is still a less ideal route as I'm sure you can imagine.
Solution 3: track the current JWT for each user in your Users table. If the JWT they possess is different from the current one, then they aren't authenticated. If the user logs out, nullify the stored JWT-data in your Users table which would equally unauthenticate JWTs for that user and force a relogin.
I'd also recommend storing a bool of "logged in" for the user. DO NOT RELY ON THIS. This would be a value to set to true when they log in, set it to false when they log out, and validate the value is 'true' if they ever pass you a JWT. This will ensure that the moment they logout they are forced to reauthenticate.
Assuming you go with solution 3:
When storing JWT data for this solution, I'm leaning towards not storing the entire JWT because it's rather large text to begin with. Alternatively just store the JWS (JWT Signature) which will make the stored value both smaller and unusable if captured for any reason.
Next, it's a hash to begin with so we could just store the last maybe 9 values (9 because int32 max is 2147483647). We just need a bit of uniqueness, not much.
Next, we could avoid the string comparison for validating that the JWS passed is the active one if we use regex to pull the integers out of the JWS and again take maybe the first 9 numbers you encounter.
Following this method, and returning to the narrative, if you were to log out your user would be marked as logged out resulting in both yourself and your S/O being required to reauthenticate. (assuming you've changed your password you're golden, otherwise it's time to contact Customer Support)
If you were to log back in, you'd get a fresh JWT and a new signature would be stored in the Users table. If your S/O were to try to use the site, they would not be authenticated with the their old JWT and would be forced to sign back in.
Trade-off: If we only store the JWS, or a part of it as I suggested, multiple users can't be signed in to the same account at once. How you feel should feel about that really depends on your app.

How to remove cookies asp.net core authorization

If I delete a user that has cookie based authorization, the will still have access to the system until he presses the logout button. Or until the cookie expires.
How to fix the situation.
This is a general problem of claims based authentication when removing access for users. Due to how it is designed, the database is not accessed on every request to verify the access. Instead, the cryptographically signed cookie is considered to be the source of truth. So when the access is removed in the database, the cookie is still valid and it is not trivial how to update the user’s cookie in that case. Since it is a cookie that is stored on the client side, you also can’t just log out the user remotely.
But there is a mechanism in ASP.NET Core Identity that enforces the cookie authentication scheme to re-validate an identity on a certain interval. You can configure this like this in your Startup’s ConfigureServices method:
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.FromMinutes(10);
});
This would set the interval to 10 minutes. So every 10 minutes, a user accessing your site with an existing cookie would be validated, and a new cookie would be issued. This process is completely silent and happens behind the scenes.
Depending on how strictly you want to enforce this, you would have to lower this interval further. I would generally recommend you to still leave it at a certain duration. Otherwise you are defeating the purpose of the cached identity.
You should also consider how problematic it really is if a user still has access to your site, and how time critical a user removal would have to be. Depending on your application, it’s also not unlikely that you retrieve the user entity within your critical actions anyway, so this would automatically fail in this case, without you having to deny access by removing the cookie.

Limiting user to one login session by unique ID in AuthCookie and checking against database table

I am creating a website that lets users run tests and hosts study content, I do not wish for any logged in user to be able to have more than one logical session. So essentially restricting simultaneous usage from multiple browsers or computers logged into the same account by invalidating any requests coming from any non-current/latest session. But I am not too sure on how to best hook into this with asp.net forms authentication.
I am thinking:
Create a db table called ActiveSessions, with rows: UserID, UniqueSessionID. Store this UniqueSessionID in the encrypted AuthCookie on the client.
If a user is not authenticated and logs in via standard login page /account/login etc. Create new UniqueSessionID and store it in the AuthCookie and in the ActiveSessions table respectively, overwriting any existing value. I presume I would have to decrypt and copy contents of the default AuthCookie forms authentication will issue, then create a new AuthCookie using this copied data and also insert my UniqueSessionID into it before returning it to the client.
In the event a request comes in for which the AuthCookie holds a UniqueSessionID that does does not match the one in the database or the record is missing for the respective user, invalidate the session and redirect browser to login page or kill session and issue error for Ajax requests.
Create some kind of scheduled service that cleans up records in the ActiveSessions table based on where User LastActivityDate exceeds some length of time etc.
Ideally I am hoping forms authentication provides some hooks where I can stick this logic in and avoid doing this with attributes over controllers/methods etc.
Also I wish to avoid using session state and its cookie entirely.
I had a very similar requirement. My situation was such that I had to make sure that user ID's were logged in from just one device at a time (Forms Authentication, by the way, not AD). When a user ID tried to log in to another device while still logged in to an existing device, it killed the session on their existing device while allowing them to log-in to the new device. The implementation I created has worked perfectly and so far, have found no flaws in the design. I wrote up a solution on my original post on Stack Overflow:
When the same user ID is trying to log in on multiple devices, how do I kill the session on the other device?

MVC3 + How to get the current logged on user's user name

I am new to MVC and actually new to web development all together. I have about 7 years of development experience but in services, database, object models, etc.. basically middle-tier and back-end development. I am trying to learn ASP.NET and decided to build a site using MVC3 for a personal site for myself. I will be hosting this from an account at dotnet-hosts.com. Here is my question... I don't have a domain and I will be using the built in membership provider. I noticed in the auto generated code that was created when I added the project template that in the AccountController in the method ChangePassword (ChangePasswordModel model) there is this line of code...
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
My question is specifically around User.Identity.Name, this looks like it would be returning the Windows user name just like Environment.UserName would. The Visual Studio template I used is the (Mobile Ready HTML5 MVC.NET) as I want to be able to support clients from any device...Windows PC, Apple, Windows Phone, iPhone, etc... If the call to User.Identity.Name is correct then I would like to ask how does this work on devices that are not Windows like an iPhone? If my assumption is correct that this will only work for Windows computers with a domain then how can I achieve this? would I need to perhaps use some caching? If so could I maybe grab the user name and their IP address to be used as the cache key from the Authentication page?
My high level question is... How do I get the current logged in user's userName regardless of the device/platform? I know this question is probably not written well and may be hard to understand... I apologize for that. I am new to web development and trying to get my feet wet and would like to start to the latest technology.
The call is correct. The User.Identity.Name is filled out by whatever authentication provider is in use - Windows authentication, Forms authentication, some custom authentication provider, or whatever. It isn't bound to a specific user "type". The authentication provider has the responsibility of making sure the Identity object corresponds to the current user on every request. Usually that part is taken care of using a combination of cookies and database.
The MVC template (although I haven't had a look at the template since MVC 2) uses ASP.NET's Membership class, which in turn uses a membership provider - for example SqlMembershipProvider or ActiveDirectoryMembershipProvider - the former stores your users' credentials (username and password etc.) in an SQL Server database, the latter uses Active Directory (i.e. primarily Windows logons). SqlMembershipProvider is the default, and MVC is set up to use a local SQLExpress database file as its user store.
The authentication provider that's implemented in the template project uses FormsAuthentication, which does the login procedure through a simple HTML form (the one in the LogOn view) and keeps the user signed in by way of an encrypted cookie. Works on any platform.
The setup for both FormsAuthentication and SqlMembershipProvider can be found in web.config (the one in the root of the site). There you can find the connection strings for the SQLExpress database (and e.g. change them to use a "real" SQL Server if needed), the timeout for logins etc.
(Note that you can do a lot of that configuration easily in a GUI through the "ASP.NET Configuration" button in the toolbar of Solution Explorer in Visual Studio - it also provides an easy way to set up the first users).
In short, it's all ready to go - and doesn't lock out non-Windows users.
Like you said User.Identity.Name is indeed correct. for returning the logged in users name. But the membership section like you said, provides only windows accounts. You can use similar without the user of windows accounts, to work in every scenario, and can still verify against windows if present. If you call it without membership, and follow the default MVC3 template it should work fine.
String Username = User.Identity.Name;
When you log on, using the template MVC3, it creates an authcookie. See account controller code. Here, two parameters are passed into it. The username, and to persist (when browser is closed - login is still cached).
The username is a string field, which is what is called by User.Identity.Name and infact, anything can be put into it, and is not in anyway linked to Windows login.
You could test the login via method you desire, and if yes, set a cookie using the authcookie method. (its encripted). And set the username to what ever you want. And if your verification of the user fails, dont create one, and redrect back to page.
See the example code. This is all from memory, as I dont have code infront of me for reference. But its all in the account controller, Login Action.
When the cookie is set, The users login state is cached for the session. You will need to ensure the user is logged in when visiting a webpage. Otherwise loggin in will be pointless. This is a simple attribute on the controller/action.
Note: dont do this to the Account/logon controller, as you wont be able to visit the logon page, as you are not logged in.
[Authorize]
public ActionResult DoSomething()
{
// ...
}
Hope I have helped.

Categories

Resources