question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug] Calls to SharePoint Online REST API Fails with Invalid issuer or signature error

See original GitHub issue

Which version of Microsoft Identity Web are you using? Microsoft Identity Web 1.7.0

Where is the issue?

  • Web app
    • Sign-in users
    • Sign-in users and call web APIs
  • Web API
    • Protected web APIs (validating tokens)
    • Protected web APIs (validating scopes)
    • Protected web APIs call downstream web APIs
  • Token cache serialization
    • In-memory caches
    • Session caches
    • Distributed caches
  • Other (please describe)

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.

  1. Client App
  2. Web API App

Provision Web API App

  • Create Azure AD App registration (named: demo-webapi-spo)
  • API Permissions: image
  • Go to ‘Expose an API’ and set app id uri and scope: image
  • Copy the scope uri and store it to use in Postman for generating the access token. Example scope uri: api://3fbcf6b8-baa1-4f47-a730-4f3c3e003b31/access_as_user
  • Below two steps will need the Client APP Azure AD App ID. Finish ‘provision client app’ steps first and then follow below steps
  • Add the AAD App ID corresponding to the Client App to the ‘Authorized client applications’ section. (see above image)
  • Edit the manifest to add the AAD App ID of the Client App to the ‘KnownClientApplications’ section: image

Provision Client App

  • Create a new Azure AD App registration (named: client-app-spo)
  • Set the redirect url to postman callback url (https://oauth.pstmn.io/v1/callback) image
  • API permissions - Add permissions to the demo-webapi-spo app for the access_as_user scope: image

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 <WebAPI_APP_ID> --tenant-id <TENANT_ID> --domain Contoso.OnMicrosoft.com -o demo-webapi-spo -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();
        }
  • Run dotnet build
  • Run dotnet run
  • You can access the API and call above method at https://localhost:5001/WeatherForecast (say in Postman client). It will give unauthorized error - which is expected.

Prepare Client App in Postman

  • I used Postman client to generate access token to call the above api.
  • Here is the config for the token generation image
  • Then used the acces token as bearer token to call the web api
  • This returns the invalid issuer or signature error image
  • Here is the error:
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:

  • Updated code to do dependency injection of ITokenAcquisition and HttpClient into our controller.
        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;
        }
  • Then updated the method to use HttpClient when if it fails using CallWebApiForUserAsync method
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}");
                }
            }        
  • Now when I call the API it successfully runs.

Additional context / logs / screenshots Add any other context about the problem here, such as logs and screenshots.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8

github_iconTop GitHub Comments

1reaction
svarukalacommented, Mar 16, 2021

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.

1reaction
jmprieurcommented, Mar 16, 2021

Thanks @svarukala In your appsettings.json, “Scopes” should be set to “https://contoso.sharepoint.com/AllSites.Read

Read more comments on GitHub >

github_iconTop Results From Across the Web

Issue with an “invalid issuer or signature” response from ...
I'm trying to make REST callouts from Salesforce to Sharepoint and I'm getting an "Invalid issuer or signature" response from my code. I...
Read more >
Invalid issuer o signature error in SPO Provider-Hosted ...
We use this issue list for tracking incoming issues around SharePoint development and issues are automatically synced to our VSO for internal ...
Read more >
OAuth 2.0 {"error_description":"Invalid issuer or signature."} ...
I'm trying to execute a REST API call in SharePoint Online. For this, I wanted to see if I can register an app...
Read more >
Invalid issuer o signature error in SPO Provider-Hosted ...
We are seeing the same issue. It looks like MS is posting back the wrong URL. In our case the application is hosted...
Read more >
Common issues when working with SharePoint Framework ...
This error is caused by insufficient CORS configuration on the API that you're calling. Because your component is communicating with the API ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found