Which version of Microsoft Identity Web are you using?
Microsoft Identity Web 1.7.0
Where is the issue?
Is this a new or an existing app?
This is a new app or an experiment
Repro
This involves onbehalfof (OBO) flow. So we need two apps.
Provision Web API App



Provision Client App


Admin consent
Construct admin consent url that looks like this:
https://login.microsoftonline.com/{Tenant-ID}/adminconsent?client_id={Client-App-AAD-App-ID}
Access this url in browser and login using an admin account to consent on behalf of the org for all users. Note that the consent prompt in addition to the access_as_user scope for the web api, should also show the permissions for the SPO Rest API too (since client app is added as knownclientapp in the Web API App).
Create project for Web API App
-Use dotnet new command to create a new project that calls SPO rest api.
dotnet new webapi2 --auth SingleOrg --called-api-url https://contoso.sharepoint.com --client-id
-Ensure the appSettings.json file is updated with correct Client secret and also set the scope to AllSites.Read
-The project has sample code auto-generated. I made a minor change by providing the 'options' to call a specific SPO site API.
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi", options => {
options.RelativePath = $"sites/ModernTeamSite/_api/web/lists";
}).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// Do something
}
else
{
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}");
}
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
Prepare Client App in Postman


System.Net.Http.HttpRequestException: Invalid status code in the HttpResponseMessage: Unauthorized: {"error_description":"Invalid issuer or signature."}
at demo_webapi_spo.Controllers.WeatherForecastController.Get() in C:\D\mg\demo-webapi-spo\Controllers\WeatherForecastController.cs:line 56
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Expected behavior
Able to call the SPO REST API without any errors
Actual behavior
Call to SPO REST API leads to this error: Unauthorized: {"error_description":"Invalid issuer or signature."}
Possible solution
When I use ITokenAcquisiton implementation and use code _tokenAcquisition.GetAccessTokenForUserAsync() to call the same SPO REST API it works just fine.
Here are the steps:
private readonly ITokenAcquisition _tokenAcquisition;
private readonly HttpClient _httpClient;
public WeatherForecastController(ILogger<WeatherForecastController> logger,
IDownstreamWebApi downstreamWebApi, ITokenAcquisition tokenAcquisition, HttpClient httpClient)
{
_logger = logger;
_downstreamWebApi = downstreamWebApi;
_tokenAcquisition = tokenAcquisition;
_httpClient = httpClient;
}
using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi", options => {
options.RelativePath = $"sites/ModernTeamSite/_api/web/lists";
}).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
methodUsed = "[CallWebApiForUserAsync]";
// Do something with apiResult
}
else
{
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "https://contoso.sharepoint.com/AllSites.Read" });
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response2 = await _httpClient.GetAsync("https://contoso.sharepoint.com/sites/ModernTeamSite/_api/web/lists");
if(response2.IsSuccessStatusCode)
{
result = await response2.Content.ReadAsStringAsync().ConfigureAwait(false);
methodUsed = "[GetAccessTokenForUserAsync]";
}
else{
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}");
}
}
Additional context / logs / screenshots
Add any other context about the problem here, such as logs and screenshots.
@svarukala, you'll need to set options.Scopes
using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi", options => {
options.RelativePath = $"sites/ModernTeamSite/_api/web/lists";
options.Scopes = "https://contoso.sharepoint.com/AllSites.Read";
}).ConfigureAwait(false);
or otherwise set it in the "DownstreamWebApi" section of the appsettings.json like done here: https://github.com/AzureAD/microsoft-identity-web/blob/1d96ee6f6434c27de58a227a96bd374240299990/tests/WebAppCallsWebApiCallsGraph/Client/appsettings.json#L21-L25
@jmprieur I have the scope configured in the appsettings.json file in my web api project from beginning but still the calls to SPO REST API were failing.

Due to current Azure AD outage I am not able to try the options.Scopes. I will try with the options.Scopes and report back here.
@jmprieur I can confirm that with the options.Scopes set the code is working fine.
But its expected to work with the appsettings.json file alone as long its properly configured. I wonder why that is failing.
@svarukala : it does!
Can you please share the line of your Startup.cs where you call .AddDownstreamWebApi() ?
@jmprieur here is the Startup.cs ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "demo_webapi_spo", Version = "v1" });
});
}
Thanks @svarukala
In your appsettings.json, "Scopes" should be set to "https://contoso.sharepoint.com/AllSites.Read"
Shall we close this issue @svarukala .?
That is it. That did it.
When I worked with MS Graph the Scopes worked with just the scope name (without the graph url), hence I expected the same with SPO.
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
}
Thank you for your help @jmprieur. Appreciate your time. Please close this issue.