Back in the SilverLight days when you created a Windows Phone app that needed to store user credentials, there was only one good way to do this!
You had to use the ProtectData class with it’s Protect and Unprotect methods.
The reference post about how to do this was given by Rob Tiffany here http://robtiffany.com/encrypting-your-credentials-on-windows-phone-7-5/!
But of course you now want to create a new Universal app and you wonder what the new way of storing credentials in a safe way is today… Well it’s called PasswordVault!
Read about it here http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.security.credentials.passwordvault.aspx.
Big advantage of using PassworVault, is that it will roam the settings across devices! So in other word, if you first installed the Windows Phone app and entered your credentials. Than download the Windows Store app, you’ll no longer need to enter your credentials because the app uses the same Vault and can already read the needed info out of it!
Now let me show you how you implement these 2 options, so you’ll have a clear overview of how to migrate from one to the other.
First up, the use of ProtectData.
What I tend to do is protect all account data in one go by first creating a json string from the given data and storing that as a protected byte array inside a file on the Windows Phone.
Small note: the Account class in the examples below is just a small POCO with UserName and Password properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
private void StoreHashedCredentials(List<Account> accountList, string fileName) { string accountListJson = JsonConvert.SerializeObject(accountList); //Convert Password and Salt values to byte[] arrays byte[] accountsByte = Encoding.UTF8.GetBytes(accountListJson); //Encrypt Password and Salt byte[] arrays using Protect() method byte[] protectedAccountsByte = ProtectedData.Protect(accountsByte, null); //Save byte[] arrays as two files in Isolated Storage SaveToFile(protectedAccountsByte, fileName); } private void SaveToFile(byte[] encryptedPasswordData, string fileName) { IsolatedStorageFile getApplicationFile = IsolatedStorageFile.GetUserStoreForApplication(); if (getApplicationFile.FileExists(fileName)) getApplicationFile.DeleteFile(fileName); IsolatedStorageFileStream fileAsStream = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Create, FileAccess.Write, getApplicationFile); Stream writer = new StreamWriter(fileAsStream).BaseStream; writer.Write(encryptedPasswordData, 0, encryptedPasswordData.Length); writer.Close(); fileAsStream.Close(); } |
When that is done, you’ll be able to read it out again when needed! Most of the time you’ll do this at app startup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
private void RetrieveHashedCredentials(out List<Account> accountList) { accountList = new List<Account>(); //Read byte[] arrays from files byte[] protectedAccountsByte = ReadFromFile(FileName); if (!ReferenceEquals(protectedAccountsByte, null)) { //Decrypt Password and Salt byte[] arrays using Unprotect() method byte[] accountsByte = ProtectedData.Unprotect(protectedAccountsByte, null); //Convert byte[] arrays to strings and display in the text boxes string accountListJson = Encoding.UTF8.GetString(accountsByte, 0, accountsByte.Length); if (!string.IsNullOrEmpty(accountListJson)) accountList = JsonConvert.DeserializeObject<List<Account>>(accountListJson); } } private byte[] ReadFromFile(string fileName) { IsolatedStorageFile getApplicationFile = IsolatedStorageFile.GetUserStoreForApplication(); byte[] data = null; if (getApplicationFile.FileExists(fileName)) { IsolatedStorageFileStream fileAsStream = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Open, FileAccess.Read, getApplicationFile); Stream reader = new StreamReader(fileAsStream).BaseStream; data = new byte[reader.Length]; reader.Read(data, 0, data.Length); reader.Close(); fileAsStream.Close(); } return data; } |
So you see it’s very easy to store a whole collection of account info in your isolated storage in a secure way!
Now when you upgrade your app to WP 8.1 RT, you’ll want to do this in a different way! By using the PasswordVault to ensure that your credentials are still stored in a secure way AND are roaming across devices.
When you want to use the PasswordVault in your app, you’ll need to select your own app key. Because everything stored and retrieved from the vault is done with that key! This is needed because otherwise, when linking your WP and Win Store app, you won’t be able to read out the same credentials.
Getting all credentials from the vault is done through the FindAllByResource method, supplying the app key. Do note that you’ll need to check for exceptions that can occur by doing this, because if the given app key is not yet present in the vault, you’ll get an exception!
After that, you’ll need to request the password for each credential that you got from the vault! Don’t forget this, because when using the FindAllByResource method the passwords will still be empty. Getting the password is done by using the RetrievePasword method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
private async Task InitializeSettingsService() { _vault = new PasswordVault(); IReadOnlyList<PasswordCredential> credentials = null; try { credentials = _vault.FindAllByResource(Constants.VAULTRESOURCENAME); } catch (Exception exception) { //Just catch the exception! When the vault has not yet setup a collection for the given resource, we get an exception! } if (credentials != null) { this.Accounts.Clear(); foreach (var passwordCredential in credentials) { passwordCredential.RetrievePassword(); this.Accounts.Add(new Account() { UserName = passwordCredential.UserName, Password = passwordCredential.Password }); } } } |
So now we already know how to get hold of everything in the vault, but how do we store or remove accounts?
Well there are 2 methods for this, Add and Remove, so as easy as that. Do note there is no Update method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
public async Task AddAccount(Account accountToAdd) { //Reinitialize the vault to see if the given account is already available await this.InitializeSettingsService(); Account accountFromVault = this.Accounts.FirstOrDefault(item => item.UserName.Equals(accountToAdd.UserName, StringComparison.OrdinalIgnoreCase)); if(accountFromVault == null) _vault.Add(new PasswordCredential(Constants.VAULTRESOURCENAME, accountToAdd.UserName, accountToAdd.Password)); if (accountFromVault != null && !accountFromVault.Password.Equals(accountToAdd.Password, StringComparison.Ordinal)) { _vault.Remove(new PasswordCredential(Constants.VAULTRESOURCENAME, accountFromVault.UserName, accountFromVault.Password)); _vault.Add(new PasswordCredential(Constants.VAULTRESOURCENAME, accountToAdd.UserName, accountToAdd.Password)); } Account accountFromMemory = this.Accounts.FirstOrDefault(item => item.UserName.Equals(accountToAdd.UserName, StringComparison.OrdinalIgnoreCase)); if (accountFromMemory != null) { if (!accountFromMemory.Password.Equals(accountToAdd.Password, StringComparison.OrdinalIgnoreCase)) { this.Accounts.Remove(accountFromMemory); this.Accounts.Add(accountToAdd); } } else this.Accounts.Add(accountToAdd); } public async Task RemoveAccount(Account accountToRemove) { //Reinitialize the vault to see if the given account is already available await this.InitializeSettingsService(); Account accountFromVault = this.Accounts.FirstOrDefault(item => item.UserName.Equals(accountToRemove.UserName, StringComparison.OrdinalIgnoreCase)); if (accountFromVault != null) _vault.Remove(new PasswordCredential(Constants.VAULTRESOURCENAME, accountToRemove.UserName, accountToRemove.Password)); Account accountFromMemory = this.Accounts.FirstOrDefault(item => item.UserName.Equals(accountToRemove.UserName, StringComparison.OrdinalIgnoreCase)); if (accountFromMemory != null) this.Accounts.Remove(accountFromMemory); } |
So there you have it, everything you need to know to convert your apps!