Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpContext.Current is null on TokenCache.BeforeAccess

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
like image 403
Thomas Therkildsen Avatar asked May 11 '17 06:05

Thomas Therkildsen


4 Answers

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));
like image 199
Harish Mashetty Avatar answered Sep 19 '22 10:09

Harish Mashetty


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.

like image 37
hula100 Avatar answered Sep 20 '22 10:09

hula100


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" />

like image 35
Thiago Custodio Avatar answered Sep 19 '22 10:09

Thiago Custodio


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.

like image 25
Bryan Roberts Avatar answered Sep 21 '22 10:09

Bryan Roberts