0
votes

I configured my MVC webapp using the built-in "Configure Azure AD Authentication" function and it provided me with the following code:

public async Task<string> GetTokenForApplication()
    {
        string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
        string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
        string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

        // 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 database
        AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
        AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
        return authenticationResult.AccessToken;
    }

And heres the model

public class ADALTokenCache : TokenCache
{
    private ApplicationDbContext db = new ApplicationDbContext();
    private string userId;
    private UserTokenCache Cache;

    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"));
    }

    // clean up the database
    public override void Clear()
    {
        base.Clear();
        var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
        db.UserTokenCacheList.Remove(cacheEntry);
        db.SaveChanges();
    }

    // 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 = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
        }
        else
        { 
            // retrieve last write from the DB
            var status = from e in db.UserTokenCacheList
                         where (e.webUserUniqueId == userId)
            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.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            }
        }
        this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
    }

    // 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 UserTokenCache
            {
                webUserUniqueId = userId,
                cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
                LastWrite = DateTime.Now
            };
            // update the DB and the lastwrite 
            db.Entry(Cache).State = Cache.UserTokenCacheId == 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
    }

    public override void DeleteItem(TokenCacheItem item)
    {
        base.DeleteItem(item);
    }
}

DBContext:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext()
        : base("AzureDatabase")
    {
    }

    public DbSet<UserTokenCache> UserTokenCacheList { get; set; }
}

public class UserTokenCache
{
    [Key]
    public int UserTokenCacheId { get; set; }
    public string webUserUniqueId { get; set; }
    public byte[] cacheBits { get; set; }
    public DateTime LastWrite { get; set; }
}

At first all the token cache were stored in a local database auto configured by the function connected to a database called "defaultconnection". However as I was trying to publish my web app I was getting errors and I realised it was because all the tokens are stored locally. So I tried to migrate the DBContext to my Azure Database. After I've migrated I keep getting this exception. Is there a way to fix this??

1
Does your database on Azure side look identical?juunas

1 Answers

0
votes

Based on my understanding, we should perform the acquire the token without using the refresh token before we call the AcquireTokenSilentAsync method.

For example, we can acquire the token after web app get the authorization code when users sign-in. Here is a piece of code for your reference:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = SettingsHelper.ClientId,
        Authority = SettingsHelper.Authority,                

        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            //
            // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.

            AuthorizationCodeReceived = (context) =>
            {
                var code = context.Code;

                ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
                String UserObjectId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(UserObjectId));

                authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, SettingsHelper.AADGraphResourceId);
                //authContext.TokenCache.Clear();
                return Task.FromResult(0);
            }
        }
    });

And you can refer the full code sample here for this scenario to use AcquireTokenSilentAsync function.