I am having difficulty in understanding how to reuse a user's security token, to authenticate them as their data request flows through multiple Web APIs.
Console App
- C# / Net Framework 4.7.x console application.WebAPI 1
- C# / .Net Core 2.2 MVC WebAPI application.WebAPI 2
- C# / .Net Core 2.2 MVC WebAPI application.
Currently, these are all configured as standalone applications in their own Visual Studio 2019 Solutions running on my dev box, but (once working!!) will each be hosted in Azure as separate entities in their own right.
Essentially, the user authenticates within the Console App, validating their credentials from Azure Active Directory. Following this sample on GitHub, I've got my Console App to call off to WebAPI 1
successfully, and returns data.
However, I want WebAPI 1
to call WebAPI 2
during the call and to retrieve other data as part of the dataset for the Console App, and this is the part I'm stuck with.
WebAPI 2
is configured in the Azure Portal exactly the same as WebAPI 1
, with the exception of different Application Client Ids, etc.
As part of the sample (mentioned above), I am able to have WebAPI 1
call off to Microsoft's Graph API, before returning data back to the calling Console App
, so I don't think I'm a way off of this. Here's the code to call the Graph API:
public async Task<string> CallGraphApiOnBehalfOfUser()
{
string[] scopes = { "user.read" };
// we use MSAL.NET to get a token to call the API On Behalf Of the current user
try
{
string accessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUser(HttpContext, scopes);
dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
return me.userPrincipalName;
}
catch (MsalUiRequiredException ex)
{
_tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeader(HttpContext, scopes, ex);
return string.Empty;
}
}
private static async Task<dynamic> CallGraphApiOnBehalfOfUserOriginal(string accessToken)
{
//
// Call the Graph API and retrieve the user's profile.
//
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.GetAsync("https://graph.microsoft.com/v1.0/me");
string content = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
dynamic me = JsonConvert.DeserializeObject(content);
return me;
}
throw new Exception(content);
}
My plan was to change the URL in the above code to point to the address of WebAPI 2
, but it fails during authentication. IF I remove the [Authorize] class attribute on my Controller within WebAPI 2
, it does successfully make a connection and return the expected data, but with the attribute on, it doesn't even hit a breakpoint on the Controller, suggesting to me that the problem is with the bearer token that I'm trying to use OR that WebAPI 2 is not configured properly.
Getting a copy of the security token and trying to re-use this mid-flight also doesn't work, as I assume that the token is for the WebAPI 1
and is therefore invalid for use with WebAPI 2
.
Should I be doing pass-along authentication like this? (It feels dirty to hard-code user credentials into WebAPI 1
which are able to access WebAPI 2
, so I don't want to do that. Plus, if the user credentials need changing, I've got a redeployment just for that.)
Is there a better way to do what I'm trying to do?
If you need me to provide more information to explain anything I've done, I certainly can do.
UPDATE 1: Here's the Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddProtectWebApiWithMicrosoftIdentityPlatformV2(Configuration)
.AddProtectedApiCallsWebApis(Configuration, new string[] { "user.read", "offline_access" })
.AddInMemoryTokenCaches();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
UPDATE 2: Similar Stack Overflow post I've since found this SO post, which @philippe-signoret describes in his answer and is exactly what I'm after.
UPDATE 3: unauthorized response when calling WebAPI 2
Here's the error message i get back from the call:
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnection+HttpConnectionResponseContent, Headers:
{
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid"
X-SourceFiles: =?UTF-8?B?*<random-looking-code>*
X-Powered-By: ASP.NET
Date: Fri, 31 May 2019 09:48:31 GMT
Content-Length: 0
}}
As I mentioned earlier, if I remove the [Authorize] attribute from my Controller's class, the call goes through as expected.