4

I am building an ASP.NET MVC web application that relies on AAD auth. I used the standard process to add auth to the project (AAD Auth through VS) and it worked perfectly fine for the past two weeks.

Today, all of a sudden it is broken:

ASP.NET MVC Error ASP.NET MVC Error 2

This occurs on ADALTokenCache initialization (standard AAD auth routine):

public ADALTokenCache(string signedInUserId)
{ 
    // associate the cache to the current user of the web app
    userId = signedInUserId;
    this.AfterAccess = AfterAccessNotification;
    this.BeforeAccess = BeforeAccessNotification;
    this.BeforeWrite = BeforeWriteNotification;
    // look up the entry in the database
    Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
    // place the entry in memory
    this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
}

Seems strange and the root cause is unknown to me – there were no changes done to the app itself other than adding more views and controllers (nothing that should be messing with auth).

Clearing browser cookies does nothing. Manually setting the MachineKey doesn’t help either.

This repro-s both locally and on the remote server once the app is deployed.

Anyone encounter this before?

Seems odd to appear without any modifications to the config.

Den
  • 16,686
  • 4
  • 47
  • 87
  • sounds like perhaps there is a Domain Name change or Issue perhaps.. can you show the actual code as well as the stacktrace that you have there.. – MethodMan Nov 06 '15 at 20:16
  • 1
    Looks like you are using local machine key to decrypt some data that comes back from the remote AD. Is that the case? If yes then if the machine key has changed at your side, you won't decrypt anything encrypted with a different key. – Wiktor Zychla Nov 06 '15 at 20:24
  • I wonder in this case if there is a good way to just reset the cache. The data should be handled through MachineKey encryption. – Den Nov 06 '15 at 20:27
  • Maybe just don't let the key change by setting a custom fixed key for the app. – Wiktor Zychla Nov 06 '15 at 20:32
  • See answer - looks like dynamic keys might be the culprit. Will investigate this further. – Den Nov 06 '15 at 21:07
  • 2
    Possible duplicate of [Error occurred during a cryptographic operation in debug](http://stackoverflow.com/questions/34650138/error-occurred-during-a-cryptographic-operation-in-debug) – Michael Freidgeim Jan 29 '17 at 11:40
  • @MichaelFreidgeim the topic you referenced is a dupe of this - check the asked dates. – Den Feb 02 '17 at 01:39
  • @DenDelimarsky: "Possible duplicate" is a way to clean-up - to close similar questions and keep one with the best answers. The date is not essential. See [Should I vote to close a duplicate question, even though it's much newer, and has more up to date answers?](//meta.stackexchange.com/q/147643) If you agree that it requires clarification please vote on [Add clarification link to "Possible duplicate" automated comment](//meta.stackexchange.com/q/281980) – Michael Freidgeim Feb 02 '17 at 05:00
  • @MichaelFreidgeim arguably, this question is more concise, to the point, and provides a different solution. If I do say so myself™ – Den Feb 02 '17 at 05:08

4 Answers4

4

The solution to this was dropping the DB.

The cache is based on a local MDF file, and changing its name in Web.config:

<add name="DefaultConnection" 
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-Den-SomeNewName.mdf;Initial Catalog=aspnet-Den-SomeNewName;Integrated Security=True" 
providerName="System.Data.SqlClient" />
Den
  • 16,686
  • 4
  • 47
  • 87
1

When I received this error it was because I had cached some bad log-in information and the authentication was struggling to retrieve it, causing an exception.

To solve this I dropped the DB from SSMS and then commented out the line:

<add name="DefaultConnection" 
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-Den-SomeNewName.mdf;Initial Catalog=aspnet-Den-SomeNewName;Integrated Security=True" 
providerName="System.Data.SqlClient" /> 

in Web.config.

Then right click on your solution and configure your Azure AD Authentication. Click finish to reconfigure it using the same setup as previously. Because you commented out the default connection it will automatically generate you a new string and create a new local database for your member information with a cleared cache.

The previous solution didn't work for me, but this did!

JCollerton
  • 3,227
  • 2
  • 20
  • 25
1

I had the same problem.

Clear the UserTokenCaches Table instead of dropping the entire database.

Usan Shrestha
  • 11
  • 1
  • 2
0

I believe there is a bug in the default implementation of the ADALTokenCache class that gets autogenerated by Visual Studio. As far as I can tell, there is no UNIQUE constraint on the WebUserUniqueId column, no .OrderByDesc(t=>t.LastWriteDate) in the call to .FirstOrDefault(c => c.WebUserUniqueId == userId) that retrieves the token from the Cache, and no purging of the cache that takes place. In other words, it's possible to authenticate multiple times against AAD and each time a new token can get written to the UserTokenCaches table in the database, but when the call to .FirstOrDefault() is made to retrieve the token with no sorting, it is not determinate which version gets retrieved, and with how SQL Server generally writes data, it is more likely that the oldest token gets retrieved from the table.

This bug could also result in an invalid_grant message (AADSTS70002) that states The refresh token has expired due to inactivity. The token was issued on ... and was inactive for ...

The fastest way to get back up and running is to purge the UserTokenCaches table in the database and have your users reauthenticate against Azure, or at least, run a DELETE UserTokenCaches WHERE LastWriteDate < ... to get rid of some of the oldest tokens.

The quick and dirty workaround for this is to purge the cache of the user's tokens in the method that sits in Startup.Auth.cs prior to the line of code that creates the AuthenticationContext and new ADALTokenCache(...) object.

db.UserTokenCacheList.RemoveRange(db.UserTokenCacheList.Where(t => t.webUserUniqueId == signedInUserID));
db.SaveChanges();

The default implementation of the UserTokenCaches table will likely have scalability issues as well with how it's constructed. The default type of webUserUniqueId is NVARCHAR(MAX) and cannot have a UNIQUE or index constraint placed on it. I would recommend changing the type to VARCHAR and of a length that holds the value correctly and to create a single UNIQUE CLUSTERED index on this column as the cacheBits column cannot be included in a small covering index. It may be optimal to limit the size of the cacheBits column as well, though I have no idea how large this or the webUserUniqueId column can grow. I also do not know how if the datatype change from NVARCHAR to VARCHAR may affect lookups from .Net through EF as I've seen autogenerated WHERE based searches promote parameters to NVARCHAR instead of VARCHAR resulting in an INDEX SCAN or TABLE SCAN operation instead of a SEEK operation. Rather than a call to .FirstOrDefault(...), I would hand optimize the call where db.UserTokenCacheList.FirstOrDefault(...) is used to ensure the correct index is used. (the following code is untested):

Cache = db.UserTokenCacheList.SqlQuery("SELECT * FROM UserTokenCacheList WHERE webUniqueUserId = @webUniqueUserId;", new System.Data.SqlClient.SqlParameter("@webUniqueUserId", System.Data.SqlDbType.VarChar({Value=signedInUserID}).FirstOrDefault();
Alan Samet
  • 1,118
  • 2
  • 15
  • 18