Search code examples
node.jsexpressazure-active-directorypassport.jspassport-azure-ad

How to issue tokens from Azure AD in a Node.js App/API?


I am building a node app with a express backend. One of the requirements is using Azure AD for authentication. I've installed the passport-azure-ad module and have set it up as the following:

import * as passportAD from "passport-azure-ad";
// ... <snip> ....
const tenantName = "<MY_TENANT_NAME>"";
const clientID = "<MY_CLIENT_ID>";

app.use(passport.initialize());
app.use(passport.session());
const bearerStrategy = new passportAD.BearerStrategy(
  {
    identityMetadata: `https://login.microsoftonline.com/${tenantName}.onmicrosoft.com/.well-known/openid-configuration`,
    clientID
  },
  (token: any, done: any) => {
    console.log(token);
    return done(null, {}, token);
  }
);
passport.use(bearerStrategy);

Then I have added authorization to a route like this:

const myHandler = () => (req, res) => return res.json({});
app.get('/my/route',
        passport.authenticate("oauth-bearer", { session: false }),
        myHandler()
);

This is returning a 401 status as expected however, I haven't been able to find documentation on how to issue a token to a client from Azure AD. I'd like to accept a POST to a login endpoint with a username and password in the body and return a Azure AD token. Is this possible?


Solution

  • The only issuer of an Azure AD token is Azure AD. You should not collect username/password in your clients, and you should not accept them in your service.

    Your client applications simply needs to use MSAL (or ADAL, or any OpenID Connect client library) to send the user to Azure AD, have them sign in, and in response get an access token for your API.

    For example, if you client were a JavaScript single-page app, with MSAL for JavaScript you could do the following:

    var userAgentApplication = new Msal.UserAgentApplication(
        '0813e1d1-ad72-46a9-8665-399bba48c201', // AppId of you client app
        null, function (errorDes, token, error, tokenType, instance) {
            // This callback only used loginRedirect OR acquireTokenRedirect.
        }
    );
    
    var scopes = ["https://api.example.com/permission.scope"];
    userAgentApplication.loginPopup(scopes).then(function (token) {
    
        // Get the signed-in user
        var user = userAgentApplication.getUser();
    
        // Get an access token for the signed-in user
        userAgentApplication.acquireTokenSilent(scopes).then(function (token) {
    
            // Use the access token to call your API
            $.ajax({
                url: 'https://api.example.com/foo',
                type: 'GET',
                dataType: 'json',
                headers: { 'Authorization': 'Bearer ' + token },
                contentType: 'application/json; charset=utf-8',
                success: function (result) {
                    // TODO: Do something cool with the API response.
                },
                error: function (error) {
                    // TODO: Do something smart if there's an error
                }
            });
        }, function (error) {
            // TODO: Silent token acquisition failed, retry with acquireTokenPopup()
        });
    }, function (error) {
        // TODO: Deal with error.
    });
    

    (Of course, you can do this for various other platforms.)