1
votes

I have a simple javascript project (SPA-application) that successfully authenticate a user with Azure AD. But when I call my Web API it returns this error:

Status 401. Bearer error="invalid_token", error_description="The signature is invalid"

Neither my web API or SPA-application is published on Azure AD, but I have registered them as two separate applications.

I have given the SPA-application permissions to access the API for both admin and users (with API permissions).

SPA-application javascript:

function acquireTokenPopupAndCallMSGraph() {
    //Always start with acquireTokenSilent to obtain a token in the signed in user from cache
    myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
        MyAccessToken = tokenResponse.accessToken;   

        callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);

        // Call the API - my code.            
        var accessToken = tokenResponse.accessToken;           
        var apiUrl = "https://localhost:44353/api/values";                       
        callAPI(apiUrl, accessToken, APICallback);


    }).catch(function (error) {
        console.log(error);
        // Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
        // Call acquireTokenPopup(popup window) 
        if (requiresInteraction(error.errorCode)) {
            myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
                callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
            }).catch(function (error) {
                console.log(error);
            });
        }
    });
}

function callMSGraph(theUrl, accessToken, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200)
            callback(JSON.parse(this.responseText));
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xmlHttp.send();
}

function callAPI(theUrl, accessToken, callback) {
    console.log("Calling the API.");
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200)
            callback(JSON.parse(this.responseText));
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xmlHttp.send();
}

The javascript code comes from this Azure AD sample code : https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2/blob/quickstart/JavaScriptSPA/index.html

startup.cs (web API)

public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
            .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));         

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseCors(builder =>
        {
            builder
                .SetIsOriginAllowed(_ => true)
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
        });

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseAuthentication();
        app.UseMvc();
    }

appsettings.json (web API)

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "[My API application ID]",
    "Domain": "[mycompany.com]",
    "TenantId": "[my tenant ID]"
  },

ValuesController.cs

 [Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2" };
    }
1
Your code is a bit minimal on the frontend side(requestObj is not explained). But as far as I can see you are using the same access token for both the graph API and your API. This won't work. You need to acquire a token specifically for your API.Alex AIT
Thanks for commenting! I used Azure´s quick start sample code for the javascript, and you can see it here: github.com/Azure-Samples/… A question though, why do I need a different access token for the API? I managed to use the same token in an another sample code with react-adal library.Olof84
Because an access token is only meant for one API, always.juunas

1 Answers

1
votes

Question : why do I need a different access token for the API?

Answer: Azure AD does not allow users to use the same access token for multiple Azure AD resources. If you want to access multiple Azure AD resources, you can get multiple access tokens for multiple resources with the refresh token. For more details, please refer to the blog.

Method : Post
URL: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Header:
       Content-Type: application/x-www-form-urlencoded
Body:
    client_id=<your app id>
    scope=<>
    refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
    grant_type=refresh_token
    client_secret=<your secret>