AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
681 stars 211 forks source link

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

Closed svarukala closed 3 years ago

svarukala commented 3 years ago

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.

  1. Client App
  2. Web API App

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 --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();
        }

Prepare Client App in Postman

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:

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

jmprieur commented 3 years ago

@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

svarukala commented 3 years ago

@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. image

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.

svarukala commented 3 years ago

@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.

jmprieur commented 3 years ago

@svarukala : it does!

Can you please share the line of your Startup.cs where you call .AddDownstreamWebApi() ?

svarukala commented 3 years ago

@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" });
            });
        }
jmprieur commented 3 years ago

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

jmprieur commented 3 years ago

Shall we close this issue @svarukala .?

svarukala commented 3 years ago

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.