RHarris
RHarris

Reputation: 11227

AAD Token from SPA app works to call Nodejs API but not .Net WebAPI

I have a SPA application that uses MSAL to acquire a token from AAD. Because MSAL works with v2 endpoint and because v2 endpoint does not currently support issuing tokens for custom API's, I'm passing the ID Token to my api and essentially treating my api as the same application. (While this has a smell to it, it does actually work -- at least with Nodejs API).

SPA app

let idToken = Msal.Storage('localStorage').getItem(Msal.Constants.idTokenKey);

this.http.configure(config => {
   config.withBaseUrl("http://localhost:3001/")
   config.withDefaults({headers: {'Authorization': 'Bearer ' + idToken}})
});

//Call API
this.http.fetch("account")
...

Node.js API

//Using express/passport
var BearerStrategy = require("passport-azure-ad").BearerStrategy;

var options = {
     identityMetadata: "https://login.microsoftonline.com/tenantid/.well-known/openid-configuration/",
     clientID: "xxxxxxx-xxxx-xxxxxxx-xxxxx",
     passReqtoCallback: false,
     validateIssuer: true,
     issuer: "http://login.microsoftonline.com/{tenantid}/v2.0"
};

app.get("/account",passport.authenticate('oauth-bearer',{session: false}),...

The above all works. Once a user authenticates with the SPA, the token is passed and the call to the Node API works.

I'm now trying to replace the Nodejs API with a .Net WebAPI. I have the following:

Startup.cs

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
   new WindowsAzureActiveDirectoryBearerAuthenticationOptions
   {
      TokenValidationParameters = new TokenValidationParameters
      {
         //Same ID as used for ClientID in Nodejs
         ValidAudience = "xxxxxx-xxxxx-xxxxx-xxxxx",
         ValidIssuer = "https://login.microsoftonline.com/{tenantid}/v2.0",
         ValidateIssuer = true,
         AuthenticationType = "WebApi" //Tried both with and without this
      },
      Tenant = "{tenantid}"  //have tried both id and name
    }
)

AccountController.cs

[Authorize]
[Route("account")]
public IHttpActionResult AccountProfile(){
   //Get Account information
   ....

   return Ok(profile);
}

However, when I point the SPA app to call the .Net api, I always get Authorization has been denied for this request .

Is there something I'm missing?

Edit

Incidentally, I've inspected the token that is being used.

The value I'm using for clientID (Nodejs) and ValidAudience (.Net) exactly match the aud claim in the token. The issuer (Nodejs) and ValidIssuer (.Net) exactly match the iss claim in the token. Lastly, the anywhere in my code where I've inserted {tenantid}, the actual value there matches exactly the tid claim in the token.

Upvotes: 2

Views: 423

Answers (1)

Alex
Alex

Reputation: 18556

We had a similar issue when switching from ADAL to MSAL and got it to work by using a similar approach like this Github project. Specifically take a look at these files:

https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/Startup.cs https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/OpenIdConnectCachingSecurityTokenProvider.cs

Update: Our Startup.cs:

        var provider = new OpenIdConnectCachingSecurityTokenProvider(
            string.Format(bc2Instace, tenant, policyId));
        var jwt = new JwtFormat(clientId, provider);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
        {
            AccessTokenFormat = jwt,
        });

Upvotes: 1

Related Questions