I am testing a webproject using OWIN and OpenID Connect against Azure AD. I am using much of the code from this sample: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
I have an issue where i get a null exception on line 27 of this file: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs
I get the exception because HttpContext.Current is null.
I can see that Load() is called from BeforeAccessNotification().
My framework version is 4.5.2 and i have <httpRuntime targetFramework="4.5.2" ... >
in my web.config.
Why is HttpContext.Current null in this context?
Updated:
The only difference i have from the sample is that my ActionResult on my controller is not async. I call AcquireTokenSilentAsync in a async Task that is called with a .Wait() from a standard ActionResult. I am working within a CMS that does not allow me to use async ActionResults.
This is OnAuthorizationCodeReceived:
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
var credential = new ClientCredential(ClientId, AppKey);
var userObjectID =
context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
var uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, GraphUrl);
}
This is the stacktrace:
[NullReferenceException: Object reference not set to an instance of an object.]
MyTest.NaiveSessionCache.Load() in C:\Workspace\MyTest\src\Website\NaiveSessionCache.cs:26
MyTest.NaiveSessionCache.BeforeAccessNotification(TokenCacheNotificationArgs args) in C:\Workspace\MyTest\src\Website\NaiveSessionCache.cs:53
Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache.OnBeforeAccess(TokenCacheNotificationArgs args) +94
Microsoft.IdentityModel.Clients.ActiveDirectory.<RunAsync>d__55.MoveNext() +3751
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenByAuthorizationCodeCommonAsync>d__48.MoveNext() +479
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenByAuthorizationCodeAsync>d__30.MoveNext() +386
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +31
MyTest.<OnAuthorizationCodeReceived>d__12.MoveNext() in C:\Workspace\MyTest\src\Website\Startup.cs:86
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) +14139265
Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +5965
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +7305
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Security.Infrastructure.<BaseInitializeAsync>d__0.MoveNext() +824
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +334
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +204
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +777
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +204
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<DoFinalWork>d__2.MoveNext() +194
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +96
System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +363
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +157
I got it working. You have to pass the HttpContextBase for creating your session cache object. HttpContext.Current becomes null as it executes on a different thread.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading;
using System.Web;
namespace AzureADWebApp
{
public class NaiveSessionCache: TokenCache
{
private static ReaderWriterLockSlim SessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
string UserObjectId = string.Empty;
string CacheId = string.Empty;
HttpContextBase HttpContext = null;
public MSALSessionCache(string userId, HttpContextBase httpContext)
{
UserObjectId = userId;
CacheId = UserObjectId + "_TokenCache";
this.HttpContext = httpContext;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
Load();
}
public void Load()
{
SessionLock.EnterReadLock();
this.Deserialize((byte[])HttpContext.Session[CacheId]);
SessionLock.ExitReadLock();
}
public void Persist()
{
SessionLock.EnterWriteLock();
// Optimistically set HasStateChanged to false. We need to do it early to avoid losing changes made by a concurrent thread.
this.HasStateChanged = false;
// Reflect changes in the persistent store
HttpContext.Session[CacheId] = this.Serialize();
SessionLock.ExitWriteLock();
}
// Empties the persistent store.
public override void Clear()
{
base.Clear();
HttpContext.Session.Remove(CacheId);
}
// Triggered right before ADAL needs to access the cache.
// Reload the cache from the persistent store in case it changed since the last access.
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
Load();
}
// Triggered right after ADAL accessed the cache.
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (this.HasStateChanged)
{
Persist();
}
}
}
}
And create your NaiveSessionCache object with a additional parameter like below in your AuthenticationCodeReceived Notification:
new NaiveSessionCache(userObjectID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase));
ADAL add configureAwait(false) for all async method starting from version 3.13.0, which results to HttpContext.Current in NaiveSession becomes NULL because it is most likely in a new thread. You can use 3.12.0 version of ADAL which works fine.
I'm not sure if it's your case, but sometimes the root cause of HttpContext.Current null is a missing key in web.config:
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
By adding the wait with your async call you are spinning up another thread in which HTTPContext.Current is null. You either need to adjust the nativesession provider to take the httpcontext as a param or change your program flow to use the await operators to make this work with in a single thread so that you can access the variables appropriately.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With