0
votes

The question in posted in StackOverflow is as follows

We have a multi-tenant Web app (ASP.NET MVC 5.2.2) which is protected by Azure AD for user authentication and Web app calls the Backend Rest APIs (ASP.NET Web API 5.2.3) which is also protected VIA OAuth 2.0 bearer token. We are using Open-ID Connect protocol in Web app using Open-ID Connect OWIN module.

We have a requirement to get the tenant’s Azure AD directory users and groups into the application store using Graph API version 1.5 .We are using Microsoft ADAL 2.0 to get access token and refresh token and store them in the ADAL Token Cache Which is extended to Redis Cache.

The design is such a way that the Web App passes the user context to the Web API which includes SignInUserId, ObjectId ,TenantId and Web Api uses this context along with Web App identity to read the Access Token which already stored in TokenCache (if expired to refresh the access token) and to get tenant AD data using this token.

           // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
           ClientCredential clientcred = new ClientCredential(clientId, appKey);
          // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
           AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.microsoftonline.com/{0}", tenantID), new CustomTokenCache(signedInUserID));
           AuthenticationResult result = await authContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
           return result.AccessToken;

When reading the token the FailedToRefreshAccessToken exception is thrown even if the token is accessed immediately from cache .

Any help would be appreciated.

Design

Exception details

Code for custom token cache

public class PerUserCache
    {
        public string userUniqueId { get; set; }
        public byte[] cacheBits { get; set; }
        public DateTime LastWrite { get; set; }

    }

    public class CustomTokenCache : TokenCache
    {

        string userID;
        PerUserCache Cache;
        ICache database = CacheFactory.GetCacheInstance();

     /// <summary>
     /// 
     /// </summary>
     /// <param name="userID"></param>
        public CustomTokenCache(string userID)
        {
            // associate the cache to the web api
            this.userID = userID;

            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            this.BeforeWrite = BeforeWriteNotification;

            // look up the entry in the DB

                Cache = database.Get<PerUserCache>(this.userID);

            // place the entry in memory
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }

        // clean up the DB
        public override void Clear()
        {
            base.Clear();
        }
    enter code here
        // 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
        void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            if (Cache == null)
            {
                // first time access
                    Cache = database.Get<PerUserCache>(userID);
            }`enter code here`
            else
            {   // retrieve last write from the DB
                var status = database.Get<PerUserCache>(userID).LastWrite;
                // if the in-memory copy is older than the persistent copy
                if (status > Cache.LastWrite)
                //// read from from storage, update in-memory copy
                {
                    Cache = database.Get<PerUserCache>(userID);
                }
            }
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }
        // Notification raised after ADAL accessed the cache.
        // If the HasStateChanged flag is set, ADAL changed the content of the cache
        void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if state changed
            if (this.HasStateChanged)
            {
                Cache = new PerUserCache
                {
                    userUniqueId = userID,
                    cacheBits = this.Serialize(),
                    LastWrite = DateTime.Now
                };
                //// update the DB and the lastwrite                
                database.Set<PerUserCache>(userID, Cache,null);
                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
        }
    }

}

1
Could you get the detailed exception message about FailedToRefreshAccessToken exception? And it is also helpful to share the code about CustomTokenCache class. - Fei Xue - MSFT
Hi @Fei Xue, edited the post - madhu
@madhu how did you fix this issue? - Sagar K

1 Answers

0
votes

It seems that there is no problem about code of CustomTokenCache. Normally, this issue was caused by not able to find the cache for the specific user.

You can verify this issue by setting break-point to the method of BeforeAccessNotification and make sure that the Cache object is not null when it was deserialized.

Also you can check the cache in Redis directly based on the userObjectID.

In addition, since you pointed that you web app store the token into the cache store in the figure above. Would you mind share the code in your web app about how to acquire the token?