共用方式為


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

Microsoft Authentication Library for Node(MSAL Node)現已成為推薦的 SDK,用於啟用註冊於 Microsoft 身份平台的應用程式的認證與授權。 本文將介紹您將應用程式從 Active Directory 認證庫(ADAL Node)遷移到 MSAL Node 所需的重要步驟。

必要條件

更新應用程式註冊設定

使用 ADAL Node 時,你很可能使用的是 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 物件,然後它會揭露你在不同認證流程中可以使用的方法(例如 acquireTokenWithAuthorizationCode 網頁應用程式)。 初始化時,唯一必須的參數是權限 URI:

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

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

在 MSAL Node 中,你有兩個選擇:如果你是在建置行動應用程式或桌面應用程式,你就要實例化一個 PublicClientApplication 物件。 建構子至少期望有一個包含該參數的clientId。 如果你沒指定權限 URI,MSAL 會預設為 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 Node 中,如果你想限制登入到任何 Microsoft Entra 帳號(和 ADAL Node 的行為一樣),可以用 https://login.microsoftonline.com/organizations

另一方面,如果你在建置網頁應用程式或守護程式應用程式,你會實例化一個 ConfidentialClientApplication 物件。 使用這類應用程式時,你還需要提供 客戶端憑證,例如客戶端秘密或憑證:

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

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

兩者PublicClientApplicationConfidentialClientApplication與 ADAL 的AuthenticationContext不同,因為它們都綁定到一個客戶端 ID 上。 這表示,如果您有不同的用戶端識別碼想要用於應用程式中,則需要為每個用戶端識別碼具現化新的 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:識別令牌權威的網址
  • validateAuthority: 一項功能,可以防止你的程式碼向潛在惡意權威請求權杖
  • cache: 設定此 AuthenticationContext 實例所使用的憑證快取。 如果未設定此參數,則會使用默認的內存快取

MSAL Node 則使用一種 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 Node 中,你首先會在應用程式註冊門戶註冊權限,然後申請資源(例如 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 Node 僅支援 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 參數在你想要的時間請求權限(因此才有 動態範圍)。 這讓使用者能夠對範圍提供 漸進式同意 。 因此,如果一開始您只是希望使用者登入您的應用程式,而且您不需要任何一種存取,就可以這麼做。 之後,如果您需要能夠讀取使用者的行事曆,您可以在取得權杖方法中要求行事曆範圍,並取得使用者的同意。 更多資訊請見: 資源與範圍

使用承諾而非回呼

在 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 內建的 非同步/等待 語法:

    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 Node 中使用您應用程式先前利用 ADAL Node 取得的有效重新整理權杖。 更多資訊請參考 刷新代幣 章節。

你也可以透過提供自己的 快取插件,將快取寫入磁碟。 快取外掛必須實作介面 ICachePlugin。 快取就像日誌記錄一樣,是設定選項的一部分,並且會在初始化 MSAL Node 執行個體時建立。

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 Authentication Extensions for Node 提供了安全機制,讓用戶端應用程式能執行跨平台的令牌快取序列化與持久化。 支援的平台包括 Windows、Mac 和 Linux。

注意

建議使用 Microsoft Authentication Extensions for Node 用於網頁應用程式,因為這可能導致規模與效能問題。 相反地,建議 Web 應用程式在會話中保存快取。

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

在 ADAL Node 中,更新代幣(RT)被公開,讓你可以透過快取這些代幣並使用 acquireTokenWithRefreshToken 所提供的方法來開發解決方案。 與 RT 特別相關的一般案例:

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

MSAL Node 與其他 MSAL 出於安全性考量,均不公開重新整理存取權杖。 反之,MSAL 會為您處理更新 token。 因此,您不再需要為此建立邏輯。 不過,你可以使用你先前從 ADAL Node 快取中取得且仍然有效的刷新權杖,來獲得新的一組 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);
});

欲了解更多資訊,請參閱 MSAL 節點範例

注意

我們建議你在使用仍有效的刷新令牌後,銷毀舊的 ADAL Node 令牌快取,並依照上述 MSAL Node acquireTokenByRefreshToken 的方法取得新一組令牌。

處理錯誤和例外狀況

使用 MSAL Node 時,最常見的錯誤類型是 interaction_required 錯誤。 此錯誤通常可以透過啟動互動式權杖取得提示來解決。 例如,在使用 acquireTokenSilent 時,如果沒有快取的刷新權杖,MSAL Node 將無法以靜默方式獲取存取權杖。 同樣地,你嘗試存取的網頁 API 可能有條件存取政策,要求使用者執行多重驗證(MFA)。 在這種情況下,觸發acquireTokenByCode來處理interaction_required錯誤,將提示使用者進行多重認證(MFA),讓他們能夠完成。

另一個常見錯誤是 consent_required,當取得受保護資源存取權杖所需的權限未獲得使用者同意時。 如同在 interaction_required 中,consent_required 錯誤的解決方法通常是啟動互動式的令牌獲取提示,使用 acquireTokenByCode 方法。

執行應用程式

一旦完成了您的變更,就會執行應用程式並測試您的驗證案例:

npm start

範例:使用 ADAL Node 與 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!`));

下一步