如何將 Node.js 應用程式從 ADAL 遷移至 MSAL

適用於節點的 Microsoft 驗證連結庫 (MSAL Node) 現在是建議的 SDK,可在 Microsoft 身分識別平台 上註冊的應用程式啟用驗證和授權。 本文涵蓋您需要執行的重要步驟,以便將應用程式從適用於節點的 Active Directory 驗證連結庫(ADAL 節點)移轉至 MSAL 節點。

必要條件

更新應用程式註冊設定

使用 ADAL 節點時,您可能會使用 Azure AD v1.0 端點。 從 ADAL 移轉至 MSAL 的應用程式應該切換至 Azure AD v2.0 端點

安裝和匯入 MSAL

  1. 透過 npm 安裝 MSAL 節點套件:
  npm install @azure/msal-node
  1. 之後,請在您的程式代碼中匯入 MSAL 節點:
  const msal = require('@azure/msal-node');
  1. 最後,卸載 ADAL 節點套件,並移除程式代碼中的任何參考:
  npm uninstall adal-node

初始化 MSAL

在ADAL Node中,您會初始化 AuthenticationContext 物件,然後公開可用於不同驗證流程的方法(例如Web acquireTokenWithAuthorizationCode 應用程式)。 初始化時,唯一的必要參數是 授權單位 URI

var adal = require('adal-node');

var authorityURI = "https://login.microsoftonline.com/common";
var authenticationContex = new adal.AuthenticationContext(authorityURI);

在 MSAL Node 中,您有兩個替代方式:如果您要建置行動應用程式或傳統型應用程式,您可以具現化 PublicClientApplication 物件。 建構函式預期至少包含 參數的clientId組態物件。 如果您未指定,MSAL 會將授權單位 URI https://login.microsoftonline.com/common 預設為 。

const msal = require('@azure/msal-node');

const pca = new msal.PublicClientApplication({
        auth: {
            clientId: "YOUR_CLIENT_ID"
        }
    });

注意

如果您在 v2.0 中使用 https://login.microsoftonline.com/common 授權單位,將允許使用者使用任何 Microsoft Entra 組織或個人 Microsoft 帳戶 (MSA) 登入。 在 MSAL 節點中,如果您想要限制登入任何 Microsoft Entra 帳戶(與 ADAL 節點的行為相同),請改用 https://login.microsoftonline.com/organizations

另一方面,如果您要建置 Web 應用程式或精靈應用程式,您會具現化 ConfidentialClientApplication 物件。 使用這類應用程式時,您也需要提供 客戶端認證,例如客戶端密碼或憑證:

const msal = require('@azure/msal-node');

const cca = new msal.ConfidentialClientApplication({
        auth: {
            clientId: "YOUR_CLIENT_ID",
            clientSecret: "YOUR_CLIENT_SECRET"
        }
    });

PublicClientApplicationConfidentialClientApplication與 ADAL 不同的AuthenticationContext是,系結至用戶端識別碼。 這表示,如果您有想要在應用程式中使用的不同用戶端標識符,則必須為每個實例具現化新的 MSAL 實例。 如需詳細資訊,請參閱: MSAL 節點的初始化

設定 MSAL

在 Microsoft 身分識別平台 上建置應用程式時,您的應用程式會包含許多與驗證相關的參數。 在 ADAL Node 中 AuthenticationContext ,物件具有有限的組態參數數目,您可以將其具現化,而其餘參數會在程式碼中自由停止回應(例如 clientSecret):

var adal = require('adal-node');

var authority = "https://login.microsoftonline.com/YOUR_TENANT_ID"
var validateAuthority = true,
var cache = null;

var authenticationContext = new adal.AuthenticationContext(authority, validateAuthority, cache);
  • authority:識別令牌授權單位的 URL
  • validateAuthority:防止程式代碼向潛在惡意授權單位要求令牌的功能
  • cache:設定這個 AuthenticationContext 實例所使用的令牌快取。 如果未設定此參數,則會使用記憶體快取中的預設值

另一方面,MSAL 節點會使用 Configuration 類型的 組態物件。 它包含下列屬性:

const msal = require('@azure/msal-node');

const msalConfig = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientSecret: "YOUR_CLIENT_SECRET",
        knownAuthorities: [],
    },
    cache: {
        // your implementation of caching
    },
    system: {
        loggerOptions: { /** logging related options */ }
    }
}


const cca = new msal.ConfidentialClientApplication(msalConfig);

在顯著差異中,MSAL 沒有停用授權單位驗證的旗標,且依預設一律會驗證授權單位。 MSAL 會將您要求的授權單位與 Microsoft 已知的授權單位清單或您在設定中指定的授權單位清單進行比較。 如需詳細資訊,請參閱: 組態選項

切換至 MSAL API

ADAL Node 中的大部分公用方法在 MSAL 節點中具有對等專案:

ADAL MSAL 備註
acquireToken acquireTokenSilent 已重新命名,現在需要 帳戶 物件
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken 適用於移轉有效的 重新整理令牌
acquireTokenWithDeviceCode acquireTokenByDeviceCode 現在將使用者程式代碼擷取抽象化(請參閱下方)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

不過,ADAL 節點中的某些方法已被取代,而 MSAL Node 則提供新的方法:

ADAL MSAL 備註
acquireUserCode N/A 與合併 acquireTokeByDeviceCode (請參閱上圖)
N/A acquireTokenOnBehalfOf 抽象 OBO 流程的新方法
acquireTokenWithClientCertificate N/A 在初始化期間不再需要憑證(請參閱 組態選項
N/A getAuthCodeUrl 抽象化端點 URL 建構的新方法

使用範圍而非資源

v1.0 與 v2.0 端點之間的重要差異在於如何存取資源。 在ADAL節點中,您會先在應用程式註冊入口網站上註冊許可權,然後要求資源的存取令牌(例如 Microsoft Graph),如下所示:

authenticationContext.acquireTokenWithAuthorizationCode(
    req.query.code,
    redirectUri,
    resource, // e.g. 'https://graph.microsoft.com'
    clientId,
    clientSecret,
    function (err, response) {
        // do something with the authentication response
    }
);

MSAL 節點僅 支援 v2.0 端點。 v2.0 端點會 採用以範圍為中心的 模型來存取資源。 因此,當您要求資源的存取令牌時,您也需要指定該資源的範圍:

const tokenRequest = {
    code: req.query.code,
    scopes: ["https://graph.microsoft.com/User.Read"],
    redirectUri: REDIRECT_URI,
};

pca.acquireTokenByCode(tokenRequest).then((response) => {
    // do something with the authentication response
}).catch((error) => {
    console.log(error);
});

以範圍為中心的模型的優點之一是能夠使用 動態範圍。 使用 v1.0 建置應用程式時,您必須註冊應用程式所需的完整許可權集(稱為 靜態範圍),讓使用者在登入時同意。 在 v2.0 中,您可以使用 scope 參數在您想要的許可權時要求許可權(因此, 動態範圍)。 這可讓使用者對範圍提供 累加同意 。 因此,如果一開始您只想讓使用者登入您的應用程式,而且您不需要任何類型的存取權,您可以這麼做。 如果您稍後需要能夠讀取使用者的行事曆,您可以接著在 acquireToken 方法中要求行事曆範圍,並取得使用者的同意。 如需詳細資訊,請參閱: 資源和範圍

使用承諾而不是回呼

在ADAL節點中,回呼會在驗證成功且取得響應之後用於任何作業:

var context = new AuthenticationContext(authorityUrl, validateAuthority);

context.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, response) {
    if (err) {
        console.log(err);
    } else {
        // do something with the authentication response
    }
});

在 MSAL 節點中,會改用承諾:

    const cca = new msal.ConfidentialClientApplication(msalConfig);

    cca.acquireTokenByClientCredential(tokenRequest).then((response) => {
        // do something with the authentication response
    }).catch((error) => {
        console.log(error);
    });

您也可以使用 ES8 隨附的 async/await 語法:

    try {
        const authResponse = await cca.acquireTokenByCode(tokenRequest);
    } catch (error) {
        console.log(error);
    }

啟用 記錄

在 ADAL 節點中,您會在程式碼的任何位置分別設定記錄:

var adal = require('adal-node');

//PII or OII logging disabled. Default Logger does not capture any PII or OII.
adal.logging.setLoggingOptions({
  log: function (level, message, error) {
    console.log(message);

    if (error) {
        console.log(error);
    }
  },
  level: logging.LOGGING_LEVEL.VERBOSE, // provide the logging level
  loggingWithPII: false  // Determine if you want to log personal identification information. The default value is false.
});

在 MSAL 節點中,記錄是組態選項的一部分,並使用 MSAL 節點實例的初始化建立:

const msal = require('@azure/msal-node');

const msalConfig = {
    auth: {
        // authentication related parameters
    },
    cache: {
        // cache related parameters
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
}

const cca = new msal.ConfidentialClientApplication(msalConfig);

啟用令牌快取

在 ADAL 節點中,您可以選擇匯入記憶體內部令牌快取。 初始化物件時 AuthenticationContext ,令牌快取會當做參數使用:

var MemoryCache = require('adal-node/lib/memory-cache');

var cache = new MemoryCache();
var authorityURI = "https://login.microsoftonline.com/common";

var context = new AuthenticationContext(authorityURI, true, cache);

MSAL 節點預設會使用記憶體內部令牌快取。 您不需要明確匯入它;記憶體內部令牌快取會公開為 和 PublicClientApplication 類別的ConfidentialClientApplication一部分。

const msalTokenCache = publicClientApplication.getTokenCache();

重要的是,使用 ADAL 節點的先前令牌快取無法傳輸至 MSAL 節點,因為快取架構不相容。 不過,您可以使用應用程式先前在 MSAL 節點中透過 ADAL 節點取得的有效重新整理令牌。 如需詳細資訊,請參閱重新整理令牌節。

您也可以提供自己的 快取外掛程式,將快取寫入磁碟。 快取外掛程式必須實作 介面 ICachePlugin。 就像記錄一樣,快取是組態選項的一部分,而且是使用 MSAL 節點實例的初始化所建立:

const msal = require('@azure/msal-node');

const msalConfig = {
    auth: {
        // authentication related parameters
    },
    cache: {
        cachePlugin // your implementation of cache plugin
    },
    system: {
        // logging related options
    }
}

const msalInstance = new ConfidentialClientApplication(msalConfig);

範例快取外掛程式可以實作如下:

const fs = require('fs');

// Call back APIs which automatically write and read into a .json file - example implementation
const beforeCacheAccess = async (cacheContext) => {
    cacheContext.tokenCache.deserialize(await fs.readFile(cachePath, "utf-8"));
};

const afterCacheAccess = async (cacheContext) => {
    if(cacheContext.cacheHasChanged) {
        await fs.writeFile(cachePath, cacheContext.tokenCache.serialize());
    }
};

// Cache Plugin
const cachePlugin = {
    beforeCacheAccess,
    afterCacheAccess
};

如果您要開發像是傳統型應用程式的公用用戶端應用程式,適用於 NodeMicrosoft 驗證延伸模組會提供安全的機制,讓用戶端應用程式執行跨平臺令牌快取串行化和持續性。 支持的平臺包括 Windows、Mac 和 Linux。

注意

Web 應用程式不建議使用適用於 Node 的 Microsoft 驗證延伸模組,因為這可能會導致調整和效能問題。 相反地,建議使用 Web 應用程式在工作階段中保存快取。

拿掉重新整理令牌的邏輯

在ADAL節點中,重新整理令牌 (RT) 已公開,可讓您藉由快取這些令牌並使用方法,來開發使用這些 acquireTokenWithRefreshToken 令牌的解決方案。 RT 特別相關的一般案例:

  • 長時間執行的服務會執行動作,包括代表使用者不再連線的使用者重新整理儀錶板。
  • 可讓用戶端將 RT 帶入 Web 服務的 WebFarm 案例(快取已完成用戶端、加密的 Cookie,而不是伺服器端)。

MSAL 節點與其他 MSAL 不會基於安全性考慮而公開重新整理令牌。 相反地,MSAL 會為您處理重新整理令牌。 因此,您不再需要為此建置邏輯。 不過,您可以使用先前從 ADAL 節點快取取得的 (仍然有效) 重新整理令牌,以使用 MSAL Node 取得一組新的令牌。 若要這樣做,MSAL Node 提供 acquireTokenByRefreshToken,這相當於 ADAL Node 的 acquireTokenWithRefreshToken 方法:

var msal = require('@azure/msal-node');

const config = {
    auth: {
        clientId: "ENTER_CLIENT_ID",
        authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
        clientSecret: "ENTER_CLIENT_SECRET"
    }
};

const cca = new msal.ConfidentialClientApplication(config);

const refreshTokenRequest = {
    refreshToken: "", // your previous refresh token here
    scopes: ["https://graph.microsoft.com/.default"],
    forceCache: true,
};

cca.acquireTokenByRefreshToken(refreshTokenRequest).then((response) => {
    console.log(response);
}).catch((error) => {
    console.log(error);
});

如需詳細資訊,請參閱 ADAL節點至 MSAL 節點移轉範例

注意

建議您在使用仍然有效的重新整理令牌,使用 MSAL Node 的 acquireTokenByRefreshToken 方法取得一組新的令牌,如上所示,建議您終結較舊的 ADAL 節點令牌快取。

處理錯誤和例外狀況

使用 MSAL 節點時,最常見的錯誤類型是 interaction_required 錯誤。 這個錯誤通常是藉由起始互動式令牌取得提示來解決。 例如,使用 acquireTokenSilent時,如果沒有快取的重新整理令牌,MSAL 節點將無法以無訊息方式取得存取令牌。 同樣地,您嘗試存取的 Web API 可能會有 條件式存取 原則,要求使用者執行 多重要素驗證 (MFA)。 在這種情況下,藉由觸發acquireTokenByCode處理interaction_required錯誤會提示使用者輸入 MFA,讓他們能夠完整處理它。

然而,您可能會遇到的另一個常見錯誤是 consent_required,當使用者未同意取得受保護資源存取令牌所需的許可權時,就會發生這種情況。 如同 在 中 interaction_required,錯誤的解決方案 consent_required 通常會使用 acquireTokenByCode 方法起始互動式令牌取得提示。

執行應用程式

變更完成後,請執行應用程式並測試您的驗證案例:

npm start

範例:使用 ADAL 節點與 MSAL 節點取得令牌

下列代碼段示範 Express.js 架構中的機密用戶端應用程式。 當使用者叫用驗證路由 /auth時,它會執行登入,透過 /redirect 路由取得 Microsoft Graph 的存取令牌,然後顯示上述令牌的內容。

使用 ADAL 節點 使用 MSAL 節點
// Import dependencies
var express = require('express');
var crypto = require('crypto');
var adal = require('adal-node');

// Authentication parameters
var clientId = 'Enter_the_Application_Id_Here';
var clientSecret = 'Enter_the_Client_Secret_Here';
var tenant = 'Enter_the_Tenant_Info_Here';
var authorityUrl = 'https://login.microsoftonline.com/' + tenant;
var redirectUri = 'http://localhost:3000/redirect';
var resource = 'https://graph.microsoft.com';

// Configure logging
adal.Logging.setLoggingOptions({
    log: function (level, message, error) {
        console.log(message);
    },
    level: adal.Logging.LOGGING_LEVEL.VERBOSE,
    loggingWithPII: false
});

// Auth code request URL template
var templateAuthzUrl = 'https://login.microsoftonline.com/'
    + tenant + '/oauth2/authorize?response_type=code&client_id='
    + clientId + '&redirect_uri=' + redirectUri
    + '&state=<state>&resource=' + resource;

// Initialize express
var app = express();

// State variable persists throughout the app lifetime
app.locals.state = "";

app.get('/auth', function(req, res) {

    // Create a random string to use against XSRF
    crypto.randomBytes(48, function(ex, buf) {
        app.locals.state = buf.toString('base64')
            .replace(/\//g, '_')
            .replace(/\+/g, '-');

        // Construct auth code request URL
        var authorizationUrl = templateAuthzUrl
            .replace('<state>', app.locals.state);

        res.redirect(authorizationUrl);
    });
});

app.get('/redirect', function(req, res) {
    // Compare state parameter against XSRF
    if (app.locals.state !== req.query.state) {
        res.send('error: state does not match');
    }

    // Initialize an AuthenticationContext object
    var authenticationContext =
        new adal.AuthenticationContext(authorityUrl);

    // Exchange auth code for tokens
    authenticationContext.acquireTokenWithAuthorizationCode(
        req.query.code,
        redirectUri,
        resource,
        clientId,
        clientSecret,
        function(err, response) {
            res.send(response);
        }
    );
});

app.listen(3000, function() {
    console.log(`listening on port 3000!`);
});
// Import dependencies
const express = require("express");
const msal = require('@azure/msal-node');

// Authentication parameters
const config = {
    auth: {
        clientId: "Enter_the_Application_Id_Here",
        authority: "https://login.microsoftonline.com/Enter_the_Tenant_Info_Here",
        clientSecret: "Enter_the_Client_Secret_Here"
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
};

const REDIRECT_URI = "http://localhost:3000/redirect";

// Initialize MSAL Node object using authentication parameters
const cca = new msal.ConfidentialClientApplication(config);

// Initialize express
const app = express();

app.get('/auth', (req, res) => {

    // Construct a request object for auth code
    const authCodeUrlParameters = {
        scopes: ["user.read"],
        redirectUri: REDIRECT_URI,
    };

    // Request auth code, then redirect
    cca.getAuthCodeUrl(authCodeUrlParameters)
        .then((response) => {
            res.redirect(response);
        }).catch((error) => res.send(error));
});

app.get('/redirect', (req, res) => {

    // Use the auth code in redirect request to construct
    // a token request object
    const tokenRequest = {
        code: req.query.code,
        scopes: ["user.read"],
        redirectUri: REDIRECT_URI,
    };

    // Exchange the auth code for tokens
    cca.acquireTokenByCode(tokenRequest)
        .then((response) => {
            res.send(response);
        }).catch((error) => res.status(500).send(error));
});

app.listen(3000, () =>
    console.log(`listening on port 3000!`));

下一步