Saturday, 7 March 2015

Azure AD Cloud silent Authentication with ADAL and TokenCache encryption

The ADAL library was launched a couple of years ago, luckily people from Microsoft like Vittorio Bertocci, have been working hard to have a library capable of doing authentication between native applications, web applications and web services (Web API). ADAL.js is in beta, it is a full working library but there are some issues need to be solved before we can have a robust library (This is when I am writing my article so March 2015) but Vittorio promised me we will have something working in this side by April 2015.
Unfortunately for me, and due to a project we are releasing before April, there is not choice, so I will have to write a wrapper around my Azure Web Api’s and skip Adal.js until we have v1.0. So you will be asking yourself “what is this guy talking about?”.
Ok, let’s go to start from a "simple" scenario, so you can have an  idea of what I am talking about. I have a Web Application (MVC 5) which is talking with few web services (Web API 2’s). All of them are register in Azure AD, so Azure AD acts as a Black Box, and manage the authentication. In this case Azure AD will take care of the MVC Web Application and the Web API’s Authentication.
So where is the problem? Well if I want to be sure that the Web Application and the Web APis share the same authentication, I will have to use refresh tokens, but because the hard work done by Vittorio’s team, now we can use Silent Tokens, so we don’t have to worry about building a whole system to refresh the tokens, but the only place where we need to worry it is about about where to store the Tokens.
It is quite common to store the tokens in Sessions, but there is a small problem with this, you lose the multi-farm factor plus WebApi’s don’t have sessions, so when you have to talk to them becomes an impossible job.
So what is the solution? store the tokens in a persistent area. If we have a native application we could store the token’s in the file system, but if we have a Web Application we could store them in Blob Storage or Databases.
Let’s go to a scenario, when someone, which is not really a user, and it is trying to get our token to access to our farm or MVC application, manage to access to the database or file system. Well, I think we will be in big trouble here. That person will have at least 20 minutes (Time when our token expires) to hack our system.
So… let’s go to make the things more difficult for this person, let’s go to inject encryption in the tokens, with double key encryption, String and Byte Array.
The following example it is a simple call from a MVC Application to a Web API 2 using silent Authentication.
...//## GETTING THE TOKEN TO BE AUTHORISE string userObjectID = ClaimsPrincipal.Current.FindFirst(userSchema).Value; AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new TokenEncryptedDatabaseCache(userObjectID, "Iamakey"); ClientCredential credential = new ClientCredential(clientId, appKey); AuthenticationResult result = authContext.AcquireTokenSilent(resourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));string authnHeader = "Authorization: Bearer " + result.AccessToken; ...


Now… check the parameter we are passing on  AcquireTokenSilent , it is a TokenCache.

This is the model we use to store the data in the database:


public class PerUserWebCache { [Key] public int EntryId { get; set; } public string WebUserUniqueId { get; set; } public byte[] CacheBits { get; set; } public DateTime LastWrite { get; set; } }
And finally this is the beauty! TokenEncryptedDatabaseCache, which encapsulates the encryption. You will need to create a dbcontext to store the model with the data…which I am going to add as well, so you can save some time.

using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Web;using System.Web.Caching;using Microsoft.IdentityModel.Clients.ActiveDirectory;using System.Security.Cryptography;using System.IO; public class TokenEncryptedDatabaseCache : TokenCache { private TokenCacheDataContext db = new TokenCacheDataContext(); string User; private PerUserWebCache Cache; private string Key; /// <summary> /// Contsructor for the TokenEncryptedDatabaseCache class /// </summary> /// <param name="user">Current User</param> /// <param name="key">Key for the encription</param> public TokenEncryptedDatabaseCache(string user, string key) { Key = key; User = user; this.AfterAccess = AfterAccessNotification; this.BeforeAccess = BeforeAccessNotification; this.BeforeWrite = BeforeWriteNotification; //## We check if the user is in our database Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); //## If that is the case we keep it in memory //## We decrypt the token this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> /// Method to clean the database /// </summary> public override void Clear() { base.Clear(); foreach (var cacheEntry in db.PerUserCacheList) db.PerUserCacheList.Remove(cacheEntry); db.SaveChanges(); } /// <summary> /// ADAL raise a notification before acces to the cache. /// Notification raised before ADAL accesses the cache. /// This is your chance to update the in-memory copy from the DB, /// if the in-memory version is stale. The token is decrypted. /// </summary> /// <param name="args"></param> void BeforeAccessNotification(TokenCacheNotificationArgs args) { if (Cache == null) { // first time access Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } else { // retrieve last write from the DB var status = from e in db.PerUserCacheList where (e.WebUserUniqueId == User) select new { LastWrite = e.LastWrite }; // if the in-memory copy is older than the persistent copy if (status.First().LastWrite > Cache.LastWrite) //// read from from storage, update in-memory copy { Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } } this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> ///Notification raised after ADAL accessed the cache. ///If the HasStateChanged flag is set, ADAL changed the content of the cache, ///At this time we encrypt the token and save it. /// </summary> /// <param name="args"></param> void AfterAccessNotification(TokenCacheNotificationArgs args) { // if state changed if (this.HasStateChanged) { Cache = new PerUserWebCache { WebUserUniqueId = User, CacheBits = Encrypt(this.Serialize()), LastWrite = DateTime.Now }; //// update the DB and the lastwrite db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified; db.SaveChanges(); this.HasStateChanged = false; } } void BeforeWriteNotification(TokenCacheNotificationArgs args) { // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry } /// <summary> /// Encription of the token. /// </summary> /// <param name="DataToEncrypt">Data to be encrypted</param> /// <returns>Data encrypted.</returns> private byte[] Encrypt(byte[] DataToEncrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream,aes.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToEncrypt, 0, DataToEncrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } /// <summary> /// Decryption of the token. /// </summary> /// <param name="DataToDecrypt">Data to be decrypted</param> /// <returns>Data decrypted</returns> private byte[] Decrypt(byte[] DataToDecrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToDecrypt, 0, DataToDecrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } }

This is the class where we have our DBContext


using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheDataContext : DbContext { public TokenCacheDataContext() : base("TokenCacheDataContext") { } public DbSet<PerUserWebCache> PerUserCacheList { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }


Initializer…

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<TokenCacheDataContext> { }

Well, I hope you enjoy, any questions let me know.

4 comments:

ninest123 said...

replica watches, louis vuitton, air jordan pas cher, longchamp pas cher, ray ban sunglasses, nike air max, oakley sunglasses, ugg boots, oakley sunglasses, nike free, nike roshe run, louboutin shoes, michael kors, louboutin, longchamp outlet, louis vuitton outlet, polo ralph lauren outlet, longchamp outlet, tiffany jewelry, tiffany and co, jordan shoes, tory burch outlet, ugg boots, air max, louis vuitton, replica watches, nike free, louis vuitton, uggs on sale, sac longchamp, burberry, chanel handbags, oakley sunglasses, polo ralph lauren outlet, louboutin outlet, longchamp, louis vuitton outlet, prada outlet, cheap oakley sunglasses, oakley sunglasses, christian louboutin outlet, louboutin pas cher, ralph lauren pas cher, ray ban sunglasses, kate spade outlet, ray ban sunglasses, nike air max, prada handbags, gucci outlet, nike outlet

ninest123 said...

burberry outlet online, nike air max, coach purses, nike air max, michael kors, timberland, new balance pas cher, true religion jeans, kate spade handbags, nike free run uk, north face, true religion jeans, ralph lauren uk, air force, hollister pas cher, ray ban uk, converse pas cher, lacoste pas cher, michael kors outlet, michael kors outlet, ugg boots, michael kors outlet, michael kors outlet, coach outlet, vans pas cher, hollister, burberry, coach outlet, nike blazer, oakley pas cher, vanessa bruno, mulberry, ugg boots, lululemon, hermes, tn pas cher, ray ban pas cher, sac guess, hogan, true religion jeans, abercrombie and fitch, true religion outlet, michael kors, nike air max, michael kors outlet, replica handbags, michael kors, nike roshe, north face, michael kors

ninest123 said...

Vlongchamp, iphone 5s cases, hollister, ferragamo shoes, herve leger, jimmy choo shoes, giuseppe zanotti, north face outlet, vans shoes, mont blanc, nike huarache, celine handbags, iphone cases, iphone 6s cases, nike trainers, lululemon, louboutin, ralph lauren, iphone 6 cases, babyliss, s5 cases, hollister, north face outlet, nfl jerseys, bottega veneta, soccer shoes, oakley, baseball bats, abercrombie and fitch, ghd, chi flat iron, valentino shoes, beats by dre, asics running shoes, wedding dresses, reebok shoes, timberland boots, ipad cases, mac cosmetics, mcm handbags, nike roshe, nike air max, insanity workout, iphone 6s plus cases, birkin bag, p90x workout, hollister, instyler, iphone 6 plus cases, soccer jerseys, new balance

ninest123 said...

barbour jackets, toms shoes, lancel, supra shoes, juicy couture outlet, moncler, moncler, louis vuitton, thomas sabo, hollister, canada goose uk, canada goose, louis vuitton, doudoune canada goose, converse outlet, canada goose outlet, replica watches, bottes ugg, canada goose outlet, nike air max, gucci, moncler, pandora jewelry, canada goose, karen millen, marc jacobs, converse, moncler, ray ban, wedding dresses, montre pas cher, ugg,ugg australia,ugg italia, moncler, pandora charms, canada goose, moncler, canada goose, ugg,uggs,uggs canada, moncler, vans, swarovski crystal, sac louis vuitton pas cher, pandora jewelry, juicy couture outlet, louis vuitton, louis vuitton, moncler outlet, coach outlet, swarovski, ugg boots uk, links of london, ugg pas cher, pandora charms