Celdus
Celdus

Reputation: 2100

Why do I get a AADSTS50196 request loop in C# when getting a access token?

I want to authenticate to Azure Active Directory via C# and retrieve much data from SharePoint. Therefore I added parallel requests and a central authentication method. In the method I do cache the token, so I only need to request the token once.

public class AuthHelper
{
    string authorityUrl = "https://login.microsoftonline.com/{0}/";
    public string TenantId { get; set; } = "<GUID>";
    public string ClientId { get; set; } = "<GUID>";
    public string Username { get; set; } = "MyUsername";
    public string UserPW { get; set; } = "MyPa$$w0rd!";
    public string HostURL { get; set; } = "http://contoso.sharepoint.com/...";

    private string tmpToken;
    public string GetJWTToken()
    {
        // get new Token from service
        if (IsTokenExpired(tmpToken))
        {
            var scopes = new List<string>();
            // parse uri, to use hostname like 'contoso.sharepoint.com' with the default rights
            var uri = new Uri(HostURL);
            var resource = uri.Scheme + Uri.SchemeDelimiter + uri.Host + "/.default";
            scopes.Add(resource);

            IPublicClientApplication app = PublicClientApplicationBuilder.Create(ClientId)
                .WithAuthority(string.Format(authorityUrl, TenantId))
                .WithTenantId(TenantId)
                .Build();

            var securePassword = new SecureString();
            foreach (char c in UserPW) securePassword.AppendChar(c);
            // following line throw the exception
            var result = app.AcquireTokenByUsernamePassword(scopes, Username, securePassword).ExecuteAsync().Result;
            tmpToken = result.AccessToken;
        }
        return tmpToken;
    }

    public bool IsTokenExpired(string token)
    {
        if (token == null)
            return true;
        // check if tmpToken is still valid
        else
        {
            var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
            var tokenDecoded = handler.ReadJwtToken(token);
            if (tokenDecoded.ValidTo < DateTime.UtcNow)
                return true;
        }
        return false;
    }
}

When requesting multiple resources in parallel from SharePoint I get the following exception:

AADSTS50196: The server terminated an operation because it encountered a client request loop. Please contact your app vendor. Trace ID: <GUID> Correlation ID: <GUID> Timestamp: ...

Upvotes: 4

Views: 1843

Answers (1)

Celdus
Celdus

Reputation: 2100

My problem was, that I used a new instance of the class for every parallel running thread. Therefore the cache was always newly created and every time the Access Token was requested.

According to Microsoft there is a limitation for requesting tokens, even for successful requests:

multiple requests (15+) in a short period of time (5 minutes) will receive an invalid_grant error

(Source: https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-breaking-changes#march-2019)

Now I implemented a global cache with a static List of my access tokens. I check based on clientId and username, but this can be extended if needed.

Replaced private string tmpToken; with:

    protected class CustTokenCacheItem
    {
        public string ClientId { get; set; }
        public string Username { get; set; }
        public string AccessToken { get; set; }
    }

    private static readonly List<CustTokenCacheItem> TokenCache = new List<CustTokenCacheItem>();
    private string tmpToken
    {
        get
        {
            return TokenCache.FirstOrDefault(t => t.ClientId == this.ClientId && t.Username == this.Username)?.AccessToken;
        }
        set
        {
            var tkn = TokenCache.FirstOrDefault(t => t.ClientId == this.ClientId && t.Username == this.Username);
            if (value == null && tkn != null)
            {
                TokenCache.Remove(tkn);
            }
            else if (tkn == null)
            {
                TokenCache.Add(new CustTokenCacheItem { AccessToken = value, Username = this.Username, ClientId = this.ClientId });
            }
            else
            {
                tkn.AccessToken = value;
            }
        }
    }

Upvotes: 5

Related Questions