Shawn Eary
Shawn Eary

Reputation: 750

MicrosoftIdentityWebApiAuthentication - Invalid Token Signature

My SharePoint Add-in runs this JavaScript to get a message from my Greeting API:

async function getGreeting() {
    // https://www.youtube.com/watch?v=P3vkerr1nW8
    var client = new Msal.UserAgentApplication(config);
    var request = {
        scopes: ['user.read'],
        prompt: 'select_account'
    };
    let loginResponse = await client.loginPopup(request);
    let tokenResponse = await client.acquireTokenSilent(request);
    var theToken = tokenResponse.accessToken;
    // https://zinoui.com/blog/jquery-ajax-headers
    $.ajax({
        url: 'https://localhost:44316/Greeting',
        type: 'GET',
        headers: {
            'Authorization': 'Bearer ' + theToken
        },
        timeout: 600000,
        success: function (theGreeting) { alert(theGreeting); },
        error: function () { alert('Error'); }
    });
}

My ASP.NET Core 3.1 controller has this code:

namespace SharePointTestAPI.Controllers
{
    // https://www.yogihosting.com/aspnet-core-enable-cors/
    [Authorize]
    [EnableCors("SharePointOnline")]
    [ApiController]
    [Route("[controller]")]
    public class GreetingController : ControllerBase
    {
        [HttpGet]
        public String Get()
        {
            return "Great Value Canned Salmon is Rich in Omega-3 and Vitamin D!!!";
        }
    }
}

If I comment out the [Authorize] attribute, an alert box pops up and shows the expected message about Walmart Salmon. Unfortunately, if I put the [Authorize] attribute back in, I see this error in a response header:

WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid"

The tokens I get back from acquireTokenSilent looks good on both the client and the server. In both cases, they decode fine at https://jwt.ms/ , so I don't know why MicrosoftIdentityWebApiAuthentication seems to be complaining that the tokens are invalid.

My ConfigureServices function in Startup.cs looks like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
        {
            // https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-5.0
            // https://www.yogihosting.com/aspnet-core-enable-cors/
            options.AddPolicy(
                "SharePointOnline",
                builder =>
                {
                    builder.WithOrigins(
                        "https://myTestTenant-3b3547c0f805ae.sharepoint.com"
                    ).AllowAnyHeader().AllowAnyMethod();
                }
            );
        }
    );


    services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");

    services.AddControllers();
}

Here is my sanitized appsettings.json:

{
/*
The following identity settings need to be configured
before the project can be successfully executed.
For more info see https://aka.ms/dotnet-template-ms-identity-platform 
*/
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "myTenantName.onmicrosoft.com",
    "TenantId": "[someGUID]",
    "ClientId": "api://[someOtherGUID]",

    "CallbackPath": "/signin-oidc"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Can someone please help me understand why MicrosoftIdentityWebApiAuthentication seems to think my authentication token is corrupt?

Upvotes: 4

Views: 1495

Answers (1)

Shawn Eary
Shawn Eary

Reputation: 750

I kept getting "Invalid Token Signature" Error because I was erroneously getting back an Access Token instead of an Id Token... I needed to change the following line in my getGreeting Function from:

var theToken = tokenResponse.accessToken;

to

var theToken = tokenResponse.idToken.rawIdToken;

After that was fixed, I kept getting "Invalid Audience" Errors which were unrelated to the signature error. To get rid of that, I think I had to create an appRoles scope in Azure AD via the "Expose an API" Section:

enter image description here

After creating that appRoles scope, I also changed the scopes request in my getGreeting function from:

scopes: ['user.read'],

to

scopes: ['api://[myClientId]/appRoles'],

I think these additional changes allowed my SharePoint Add-in to get a Token from my API instead of Microsoft Graph. Once I made the above two changes, my API returned the expected greeting to my SharePoint Add-in.

My new getGreeting function is shown below:

async function getGreeting() {
    // https://www.youtube.com/watch?v=P3vkerr1nW8
    var client = new Msal.UserAgentApplication(config);
    var request = {
        scopes: ['api://[myClientId]/appRoles'],
        prompt: 'select_account'
    };
    let loginResponse = await client.loginPopup(request);
    let tokenResponse = await client.acquireTokenSilent(request);
    var theToken = tokenResponse.idToken.rawIdToken; 
    // https://zinoui.com/blog/jquery-ajax-headers
    $.ajax({
        url: 'https://localhost:44316/Greeting',
        type: 'GET',
        headers: {
            'Authorization': 'Bearer ' + theToken
        },
        timeout: 600000,
        success: function (theGreeting) { alert(theGreeting); },
        error: function () { alert('Error'); }
    });
}  

Lastly, I changed my ClientId in the appsettings.json file of my Web API from:

"ClientId": "api://[someOtherGUID]",

back to (without the leading api://):

"ClientId": "[someOtherGUID]",

My final appsettings.json is shown here:

{
/*
The following identity settings need to be configured
before the project can be successfully executed.
For more info see https://aka.ms/dotnet-template-ms-identity-platform 
*/
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "myTenantName.onmicrosoft.com",
    "TenantId": "[someGUID]",
    "ClientId": "[someOtherGUID]",

    "CallbackPath": "/signin-oidc"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Upvotes: 3

Related Questions