Using an AutoRest C# client to access a Web API with a bearer token - TokenCredentials does not work

bkwdesign picture bkwdesign · Jul 11, 2017 · Viewed 7.4k times · Source

EDIT 10/24 I think this was all likely user error - see my answer below for remedy before getting too deep into this question

TL;DR: For my OAuth 2.0 code flow...

Why does my TokenCredentials not work with my AutoRest client? I'm getting NO bearer token applied to the request / no Authorization header set

I know my pipeline works already..

Using code from this azure sample, which is NOT an AutoRest client, I can successfully get my access_token and can get JSON from my protected Web API project.. so I've ruled out all the prerequisite stuff.. I know my pipeline works

My AutoRest setup..

1.) Downloaded from GitHub this AutoRest repo v1.1.0

2.) Downloaded my swagger JSON to disk, saved as swagger.json

3.) Ran this command-line to generate C# files:

autorest --input-file=swagger.json --csharp --output-folder=MyCorp_ApiClient_Tsl --namespace='MyCorp.ApiClient' --add-credentials

4.) Copied generated classes into my .NET 4.6.2 web site

5.) These are my NuGets:

- Microsoft.Rest.ClientRuntime version="2.3.8" 
- Microsoft.Rest.ClientRuntime.Azure.Authentication version="2.3.1" 
- Microsoft.IdentityModel.Clients.ActiveDirectory version="2.28.3" 

Here's what's not working:

   AdalTokenHelper tokenHelper = new AdalTokenHelper();//helper code further below

    string token = await tokenHelper.GetTokenString();
    var svcClientCreds = new TokenCredentials(token, "Bearer");

    client = new MyCorp.ApiClient(new Uri(apiRsrcUrl), svcClientCreds, 
    new DelegatingHandler[] { new MyAzureTracingHandler() });

    //make call to OData controller...        
    MyCorp.ApiClient.Models.ODataResponseListStatus statusList = await client.Status.GetStatusAsync(expand: "StatusType",cancellationToken: defaultCancelThreadToken);

    return View(statusList.Value);

I've tried variations of the above, using different ctor's of TokenCredentials, but no matter, I can put my breakpoint in MyAzureTracingHandler and see the request has no Authorization headers applied.. so I get the expected 401 Unauthorized response.

If I modify MyAzureTracingHandler to accept my instance of TokenCredentials then I can force the request to have the appropriate bearer token applied..

This works, but, feels hack-ish:

I changed my original client instantiation snippet from this:

 client = new ApiClient(new Uri(apiRsrcUrl), svcClientCreds, 
 new DelegatingHandler[] { new MyAzureTracingHandler() });

To this:

 client = new ApiClient(new Uri(apiRsrcUrl), svcClientCreds, 
 new DelegatingHandler[] { new MyAzureTracingHandler(svcClientCreds) });

And inside the SendAsync method of MyAzureTracingHander I do this:

await svcClientCreds.ProcessHttpRequestAsync(request, cancellationToken);

Am I doing something wrong? I don't think I should have to pass the ServiceClientCredentials in twice when instantiating my client.

Appendix A - Getting access token via ADAL:

    private string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
    private string tslResourceID = ConfigurationManager.AppSettings["ross:TslWebApiResourceId"];
    private static string loginRedirectUri = ConfigurationManager.AppSettings["ross:LoginRedirectUri"];

    private AuthenticationContext authContext;
    private AuthenticationResult authenticationResult;

    public async Task<string> GetTokenString()
    {
        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;

        try
        {
            // 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
            authContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID));


            UserIdentifier userIdentifier = new UserIdentifier(userObjectID, UserIdentifierType.UniqueId);

            authenticationResult = await authContext.AcquireTokenSilentAsync(tslResourceID, clientcred, userIdentifier);
        }
        catch(AdalException ex)
        {
            throw ex;
        }
        return authenticationResult.AccessToken;
    }

Answer

bkwdesign picture bkwdesign · Jul 17, 2017

While I believe I ran my autorest command with --add-credentials it's possible I may have used the older syntax... --AddCredentials true

I also did not run autorest --reset as the docs recommend you do

One of these is the culprit, because now my 1.1.0 autorest installation is generating everything correctly.