共用方式為


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

適用於節點的 Active Directory 驗證程式庫 (MSAL 節點) 現在是建議的 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 節點中,您會初始化 AuthenticationContext 物件,然後公開您可以在不同驗證流程中使用的方法 (例如,適用於 Web 應用程式的 acquireTokenWithAuthorizationCode)。 初始化時,唯一的必要參數是授權單位 URI

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

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

在 MSAL 節點中,您反而有兩個替代方案:如果您要建置行動裝置應用程式或傳統型應用程式,則可以具現化 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"
        }
    });

不同於 ADAL 的 AuthenticationContextPublicClientApplicationConfidentialClientApplication 都會繫結至用戶端識別碼。 這表示,如果您有不同的用戶端識別碼想要用於應用程式中,則需要為每個用戶端識別碼具現化新的 MSAL 執行個體。 詳細資訊請參閱:MSAL 節點的初始化

設定 MSAL

在 Microsoft 身分識別平台上建置應用程式時,您的應用程式會包含許多與驗證相關的參數。 在 ADAL 節點中,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 節點中的大部分公用方法在 MSAL 節點中都有對等項目:

ADAL MSAL (部分機器翻譯) 備註
acquireToken acquireTokenSilent 已重新命名,且現在預期帳戶物件
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken 有助於移轉有效的重新整理權杖
acquireTokenWithDeviceCode acquireTokenByDeviceCode 現在將使用者程式碼取得抽象化 (請參閱下文)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

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

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 中,您可以使用範圍參數,在您需要時要求權限 (因此,這就是「動態範圍」)。 這可讓使用者對範圍提供增量同意。 因此,如果一開始您只是希望使用者登入您的應用程式,而且您不需要任何一種存取,就可以這麼做。 之後,如果您需要能夠讀取使用者的行事曆,您可以在取得權杖方法中要求行事曆範圍,並取得使用者的同意。 如需詳細資訊,請參閱:資源和範圍

使用承諾而非回呼

在 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 節點預設提供記憶體內權杖快取。 您不需要明確地將其匯入;記憶體內部權杖快取會公開為 ConfidentialClientApplicationPublicClientApplication 類別的一部分。

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
};

如果您要開發傳統型應用程式之類的公用用戶端應用程式 (部分機器翻譯),適用於節點的 Microsoft 驗證延伸模組 (英文) 會為用戶端應用程式提供安全機制,以執行跨平台權杖快取序列化和保存。 支援的平台為 Windows、Mac 和 Linux。

注意

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

移除圍繞重新整理權杖的邏輯

在 ADAL 節點中,公開重新整理權杖 (RT),可讓您開發有關使用這些權杖的解決方案,其做法是快取這些權杖並使用 acquireTokenWithRefreshToken 方法。 與 RT 特別相關的一般案例:

  • 長時間執行的服務,其執行的動作包括在使用者不再連線的情況下,代表使用者重新整理儀表板。
  • Web 伺服陣列案例,可讓用戶端將 RT 帶至 Web 服務 (快取是在用戶端進行 (加密的 cookie),而不是在伺服器端進行)。

MSAL 節點 (以及其他 MSAL) 基於安全性原因,不會公開重新整理權杖。 相反地,MSAL 會為您處理重新整理權杖。 因此,您不再需要為此建立邏輯。 不過,您可以使用先前從 ADAL 節點快取中取得 (而且仍然有效) 的重新整理權杖,以使用 MSAL 節點來取得新的一組權杖。 若要這樣做,MSAL 節點會提供 acquireTokenByRefreshToken,其相當於 ADAL 節點的 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 節點的 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 Node

下列程式碼片段示範 Express.js 架構中的機密用戶端 Web 應用程式。 其會在使用者叫用驗證路由 /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!`));

下一步