2
votes

I'm trying to adapt the sample B2C code referenced here and here to work against our organizations AAD.

I have a SPA app that is successfully authenticating with AAD via MSAL and receiving a token. The SPA app can use the token to pull data from the MS Graph API -- so I'm certain the token is valid.

The SPA is currently running locally @ localhost:9000.

I have a second app that is a Nodejs API running @ localhost:3000. The SPA app makes a call to the API passing the same token used for the GraphAPI. The API app is supposed to use that token to authenticate the user and provide access to the API; however, I only ever get back a 401 - Unauthorized.

(I'm using Aurelia for my client side code. HTTP request is made using Aurelia's HTTP client).

Here is the SPA code used to call the API:

//Not entirely sure what to use as the scope here!!  API is registered to allow user.read Graph API scope.
this.config.msClient.acquireTokenSilent(["user.read"]).then(token => {
   console.log("Token acquired silently.");
   this.makeAPICall(token);
}, error => { ... }
);

makeAPICall(token) {
   this.http.configure(config => {
     config
       .withBaseUrl('http://localhost:3000/')
       .withDefaults({
           headers: {
               'Authorization': 'Bearer ' + token
           }
      }

      this.http.fetch("user/0")
       .then(response => {
          debugger;  //response is 401 Unauthenticated at this point
       }, error => { ... });

}

I've registered an Application at apps.dev.microsoft.com for my API. And here is my server side (API) Nodejs code:

const express = require("express")
const port = 3000
const passport = require("passport")
var BearerStrategy = require("passport-azure-ad").BearerStrategy;

var userList = [
    {id: 1, first: "Mickey", last: "Mouse", age: 95},
    {id: 2, first: "Donald", last: "Duck", age: 85},
    {id: 3, first: "Pluto", last: "leDog", age: 70}
]

var options = {
    identityMetadata: "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
    clientID: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",  //My registered App Id
    passReqToCallback: false,
    validateIssuer: true,
    issuer: "xxxxxxx"  //Our tenant name
};

var strategy = new BearerStrategy(options, (token, done) => {
    console.log(token);
    done(null,{}, {scope: '*'});
})


const app = express()
app.use(passport.initialize())
passport.use(strategy);

//Allow CORS
app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin","*");
    res.header("Access-Control-Allow-Headers","Authorization, Origin, X-Requested-With, Content-Type,Accept");
    next();
})

app.get("/",(request, response) => {  //Unauthenticated -- always works
    response.send("Hello World!")
})

app.get("/user/:id",passport.authenticate('oauth-bearer',{session: false }),
  (request, response) => {
    //Never seem to get here (when debugging) as authenticate always fails
    let id = request.params["id"];
    let user = userList[id];
    if(user) response.json(user);
    else response.send("No user found")
})

app.listen(port, () => {
    console.log("Listening on port " + port)
})

I'm sure I'm just misunderstanding how this all works but would appreciate some guidance as to what I need to change to get this to work.

2

2 Answers

1
votes

Tokens are like bank checks: they can only be cached by the person they were written for. A token issued for the MS Graph will have as audience the Microsoft Graph, and should be accepted by the Microsoft Graph only. Any other API X receiving that token should refuse it. A client intending to invoke X should request a token for X, regardless of whether it already has a token for another service (like the Microsoft graph). Here there's an example of that flow, implemented with ADAL JS: https://azure.microsoft.com/en-us/resources/samples/active-directory-angularjs-singlepageapp-dotnet-webapi/ At this point Azure AD v2 (used by MSAL) is not capable of issues access tokens for custom API: the feature is being worked on, but we have no ETA for when it will become available in production. Until then, this topology isn't supported with v2 (hence MSAL JS). Sorry!

0
votes

A year later and it is now possible to secure your custom API with MSAL. In the app portal (https://apps.dev.microsoft.com) you can create your app, then "add a platform" twice - once for your web app SPA, and another for your web api. In the web api, create a "scope" e.g. "api://e892d79e-03e7-4e5e-.../access_as_user", and then pass this scope into the acquireTokenSilent call (instead of "user.read" in the example above).