Local storage is not right place to store tokens. But this blog post says LocalCache is generally the right location. If I store in LocalCache using DPAPI, Does this enough secure?
Does PasswordVault is good place to store it?
How can I store token securely so that outside this application token is not accessible?
I would definitely recommend storing confidential information like an Access Token in the PasswordVault as LocalSettings are not encrypted and are accessible quite easily from the app's package folder in AppData.
Although PasswordVault has a bit odd API, you can still easily use it to store the token:
var passwordVault = new PasswordVault();
passwordVault.Add(new PasswordCredential("Resource", "UserName", accessToken));
In your case, you most likely care only about the access token, so the "resource" and "user name" may be just arbitrary constants. Retrieving the token is easy as well:
//find credentials in the store
PasswordCredential? credential = null;
try
{
// Try to get an existing credential from the vault.
credential = _passwordVault.Retrieve("Resource", "UserName");
}
catch (Exception)
{
// When there is no matching resource an error occurs, which we ignore.
}
credential?.RetrievePassword();
return credential?.Password;
Note the use of try..catch. This is because the vault throws if given resource/user name combo is not found (which could even happen when user manually deletes the entry in system Credential Manager.
Another advantage of PasswordVault is that credentials are synced across devices (although this feature may be going away in future versions).
Where to store access token securely in UWP application?
In general, we often store access token with ApplicationData.LocalSettings class that place settings container in the local app data store. You could use it like the following.
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
// Create a simple setting.
localSettings.Values["accesstoken"] = token;
// Read data from a simple setting.
Object value = localSettings.Values["accesstoken"];
if (value == null)
{
// No data.
}
else
{
// Access data in value.
}
And if you want to store access token securely. The Windows Runtime provides the PasswordVault class to securely store credentials. for more please refer this document.
Related
I have an netcoreapp2.2 containerized application that uses azure key vault to store keys and also uses:
app.UseAuthentication();
And
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
I am building/running a docker image in a hosted linux environment under App Services. I am using the azure container registry and dev ops pipe line to maintain my app. Azure controls the deployment process and the "docker run" command.
My app works great, however in the container logs I see:
2019-12-13T17:18:12.207394900Z [40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
2019-12-13T17:18:12.207436700Z No XML encryptor configured. Key {...} may be persisted to storage in unencrypted form.
...
2019-12-13T17:18:14.540484659Z Application started. Press Ctrl+C to shut down.
I realize there are many other posts on this that allude to using other storage mechanisms, however I am using key vault to store my sensitive data. JWT is all handled by key vault. I have a few application settings that control static variables for DEV/QA/PROD but they are not sensitive data at all.
I am also not sure what key is being stored in memory as all my sensitive keys are completely outside of the application and are called by:
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
$"https://{builtConfig["MY_KEY_VAULT_ID"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
I am having a difficult time understanding why this warning is being thrown and also if I should take additional steps to mitigate the issue. I have not personally seen side effects, and app restarts do not seem to have any effect as I am using bearer tokens and other concerns such as token expiration, password resets and the like are not applicable.
So I am left with asking are there any additional steps I can take to avoid this warning? Do I need to ensure that there is a better data at rest mechanism for any configuration settings that may be in my linux environment? Can I safely ignore this warning?
It took me a while to find a way that suited the needs that I have for my application but I wanted to lend some clarity to a number of other stack answers that just did not make sense to me and how I finally understood the problem.
TLDR; Since I was already using key vault, I was confusing how .net core works. I didn't realize that config.AddAzureKeyVault() has nothing to do with how .net core decides to store data at rest on your app service.
When you see this warning:
No XML encryptor configured. Key {GUID} may be persisted to storage in unencrypted form.
it really doesn't matter what GUID was being set: that string of data was not being stored encrypted at rest.
For my risk analysis any information that is not being encrypted at rest is a bad idea as it could mean at anytime in the future some sort of sensitive data could leak and then be exposed to an attacker. In the end, I chose to classify my data at rest as sensitive and err on the side of caution with a potential attack surface.
I have been struggling to try and explain this in a clear and concise way and it is difficult to sum up in a few words. This is what I learned.
Access control (IAM) is your friend in this situation as you can declare a system assigned identity for your application and use role based accessed control. In my case I used my application identity to control access to both key vault and azure storage with RBAC. This makes it much easier to get access without SAS tokens or access keys.
Azure storage will be the final destination for the file you are creating, but it will be the vault that controls the encryption key. I created an RSA key in key vault, and that key is what encrypts the XML file that is throwing the original error.
One of the mistakes I was making in my head was that I wanted two write the encrypted XML to key vault. However, that is not really the use case Microsoft describes. There are two Mechanisms: PersistKeysTo and ProtectKeysWith. As soon as I got that through my thick head, it all made sense.
I used the following to remove the warning and create encrypted data at rest:
services.AddDataProtection()
// Create a CloudBlockBlob with AzureServiceTokenProvider
.PersistKeysToAzureBlobStorage(...)
// Create a KeyVaultClient with AzureServiceTokenProvider
// And point to the RSA key by id
.ProtectKeysWithAzureKeyVault(...);
I had already used RBAC for my application with key vault (with wrap/unwrap permissions), but I also added Storage Blob Data Contributor to the storage account.
How you create your blob is up to you, but one gotcha is creating the access token synchronously:
// GetStorageAccessToken()
var token = new AzureServiceTokenProvider();
return token.GetAccessTokenAsync("https://storage.azure.com/")
.GetAwaiter()
.GetResult();
Then I called it from a method:
var uri = new Uri($"https://{storageAccount}.blob.core.windows.net/{containerName}/{blobName}");
//Credentials.
var tokenCredential = new TokenCredential(GetStorageAccessToken());
var storageCredentials = new StorageCredentials(tokenCredential);
return new CloudBlockBlob(uri, storageCredentials);
After this hurdle was overcame, putting the encryption in was straight forward. The Keyvault ID is the location of the encryption key you are using.
https://mykeyvaultname.vault.azure.net/keys/my-key-name/{VersionGuid}
And creating the client is
var token = new AzureServiceTokenProvider();
var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(token.KeyVaultTokenCallback));
services.AddDataProtection()
.ProtectKeysWithAzureKeyVault(client, keyVaultId);
I also have to give credit to this blog: https://joonasw.net/view/using-azure-key-vault-and-azure-storage-for-asp-net-core-data-protection-keys as this pointed me in the right direction.
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/default-settings?view=aspnetcore-2.2 this also pointed out why keys are not encrypted
https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles - RBAC for apps
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1 this was confusing at first but has a good warning about how to grant access and limit access in production.
Might be you have to configure your data protection policy to use CryptographicAlogrithms as follow:
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
Also, following are few warning which you get around Data protection policy
ASP.Net core DataProtection stores keys in the HOME directory (/root/.aspnet/DataProtection-Keys) so when container restart keys are lost and this might crash the service.
This can be resolve by persisting key at
Persist key at the persistent location (volume) and mount that volume
to docker container
Persist key at the external key store like Azure or Redis
More details about ASP.NET DataProtection:
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-3.1
To mount an external volume (C:/temp-kyes) to docker container volume (/root/.aspnet/DataProtection-Keys) using following command
docker run -d -v /c/temp-keys:/root/.aspnet/DataProtection-Keys container-name
Also, You need to update your Starup.cs - ConfigureServices to configure DataProtection policy
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(#"C:\temp-keys\"))
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
The application uses OAuth2 flow to login on the users' O365 accounts and to store the returned access tokens in the session variable. The following code is used to store the tokens:
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
Request.Query["code"],
loginRedirectUri,
new ClientCredential(ConfigSettings.ClientId, ConfigSettings.ClientSecret),
ConfigSettings.O365UnifiedAPIResource);
var authResultEWS = await authContext.AcquireTokenByAuthorizationCodeAsync(
Request.Query["code"],
loginRedirectUri,
new ClientCredential(ConfigSettings.ClientId, ConfigSettings.ClientSecret),
ConfigSettings.EWSAPIResource);
HttpContext.Session.SetString(SessionKeys.Login.AccessToken, authResult.AccessToken);
HttpContext.Session.SetString(SessionKeys.Login.EWSAccessToken, authResultEWS.AccessToken);
And here is how we get the tokens back in our controllers:
private string GetSessionValue(string key)
{
byte[] buffer = new byte[2048];
HttpContext.Session.TryGetValue(key, out buffer);
return System.Text.Encoding.UTF8.GetString(buffer);
}
This soluton works on a local 5 nodes cluster but once published on an Azure 3 node cluster, the Session does not seem to work.
I used remote debugging and the access tokens are correctly added but once I call GetSessionValue, HttpContext.Session contains 0 key.
If using HttpContext.Session is a bad idea for distributed architectures like SF, what would be a good replacement solution ?
By default Session data is scoped to the node it runs on. In order to have a highly available (distributed) solution, you'd need to take the data and replicate it to other nodes.
Service Fabric Reliable Stateful Services and Actors have such mechanisms built in. You could use one of those to cache your (protected) access tokens. (and optionally serve as a gateway to O365)
I've looked through the source code and tests, but don't see a way to revoke a token. I need to cover scenarios where I disable access for a user and need that occur immediately.
Given that the token is stored in keyChain.bin, it might be possible to deserialize the collections, detokenize all tokens, remove the desired on, the serialize the collection again. This sounds elaborate. Are there any other methods I could use?
Update
Potentially I can keep a separate list of user ids and the token that they have been issued, then match the token with the keyChain collection.
Update 2
After playing with the keyChain file, things are little more confusing. After creating a new user, I issue a token:
var newServiceIdentity = siteSecurityManager.CreateServiceUser(user.UserId, user.Password)
.GetServiceIdentity();
string token = _tokenizer.Tokenize(newServiceIdentity, this.Context);
newServiceIdentity.Token = token;
siteSecurityManager.RegisterToken(newServiceIdentity);
var fileStore = new FileSystemTokenKeyStore(_rootPathProvider);
var allKeys = fileStore.Retrieve() as Dictionary<DateTime, byte[]>;
return new { Token = token };
By default, Nancy will store each token in a binary file so that should your server need to be bounced, the user sessions will survive. With a different browser session, I connect with the new users credentials and gain access to the site. When I add another user, I would expect that the allKeys count would incremented to reflect my admin session as well as the new user that is connected with a different browser. I see a count of one, and the key matches the admin token.
My login method does indeed issue a token for each user that connects with correct credentials. That logic is:
var userServiceIdentity = ServiceIdentityMapper.ValidateUser(user.UserId, user.Password);
if (userServiceIdentity == null)
{
return HttpStatusCode.Unauthorized;
}
else
{
string token = _tokenizer.Tokenize(userServiceIdentity, this.Context);
return new { Token = token };
}
I store the token and return it with each Ajax call. The implication here is that I do have the tokens recorded, otherwise authentication would fail. But if the keyChain.bin is not updated, then I can't pursue the idea of registering the token and user in a separate store, then purging that token to revoke access.
As explained to me by Jeff, the code's author, the keyChain.bin stores only the key that is used to generate the token. This is so that the relevant information is only stored on the client, and a simple comparison of the client token is used to avoid querying a back-end source. Jeff's complete explanation is here
A possible solution would be to keep a separate list of black listed tokens / users. Perhaps this is best dictated by business practices. There are indeed times where you should lock a user out of immediately. This could be accomplished by issuing a purge for all tokens, and force a login for all legitimate users. A minor inconvenience for some scenarios, and unacceptable for others.
Little prehistory:
I develop RESTful services. That services receives requests from the web frontend and resends it to another server with the actual business logic. I use Shiro to protect my services. Problem is that some business logic functions require a user password. Of course, I can store password in my principal, but I think it is not correct to store credentials there.
Question
So, what is the conceptual right place where I should store credentials to have access inside my REST services?
Update
Ok, I can also store passwords in Shiro sessions, but i don't think that it is the correct place.
Normally, the info is kept in an implementation of AuthenticationToken. This interface has two method: getPrincipal (for example login or email) and getCredentials(). The last is usually used to store a password.
If you look at class UsernamePasswordToken, which is an implementation of this interface, you see that the two are indeed used for username and password.
Now what we did is extend the class AuthorizingRealm for our own authentication mechanism and in the authentication method we store the token in the principal.
#Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
... authentication logic
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(login, realmName);
principalCollection.add(token, realmName);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principalCollection, login.getPasswordHash());
return simpleAuthenticationInfo;
}
Now you can get the token later:
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
AuthenticationToken token = principals.oneByType(AuthenticationToken.class);
I have a simple jQuery mobile PhoneGap application which uses ajax call to server to authenticate a users credentials against servers db (uses CORS).
Once user is authenticated I setup some local storage variables for the session but I suspect this is not a secure method of maintaining state.
I'm wondering if there is a better & more secure way to keep track of the users session state. At the moment I'm thinking of implementing some kind of token based handshake between the app & the server for each subsequent server call post logon. I'm hoping there is a better more standard way to implement secure sessions in PhoneGap.
I think you shouldn't save origin credentials in local storage. At first, you should encrypt credentials by your encrypt algorithms and you can save it in local storage or preference(Android/iOS - best is store in preference) or anywhere you want. If another person get credentials, it have been encrypt, they not use it to authenticate with server(Only you had decrypt algorithms).
Continue, you must do something to prevent other person decompile your app. They will have your decrypt algorithms and use it to decrypt credentials-->origin credentials-->post to server success.
Have many way to secure your app, you can search and research.
Code demo in Android:
SharedPreferences sharedPref = getSharedPreferences("account", Context.MODE_PRIVATE);
Editor editor = sharedPref.edit();
user = your_encrypt(username);
pass = your_encrypt(password);
//encrypt and save account
editor.putString("username", user);
editor.putString("password", pass);
editor.commit();
//get account
String u = sharedPref.getString("username", "");
String p = sharedPref.getString("password", "");
//decrypt username/pass
username = your_decrypt(u);
password = your_decrypt(p);