Bagikan melalui


Cara memigrasikan aplikasi JavaScript dari ADAL.js to MSAL.js

Pustaka Autentikasi Microsoft untuk JavaScript (MSAL.js, juga dikenal sebagai msal-browser) 2.x adalah pustaka autentikasi yang kami sarankan untuk digunakan dengan aplikasi JavaScript di platform identitas Microsoft. Artikel ini menyoroti perubahan yang perlu Anda lakukan untuk memigrasikan aplikasi yang menggunakan ADAL.js agar menggunakan MSAL.js 2.x

Catatan

Kami sangat menyarankan MSAL.js 2.x daripada MSAL.js 1.x. Alur pemberian kode autentikasi lebih aman dan memungkinkan aplikasi satu halaman mempertahankan pengalaman pengguna yang baik meskipun ada langkah-langkah privasi yang diterapkan browser seperti Safari untuk memblokir cookie pihak ke-tiga, di antara manfaat lainnya.

Prasyarat

  • Anda harus mengatur Jenis URL Balasan Platform / ke Aplikasi satu halaman di portal Pendaftaran Aplikasi (jika Anda memiliki platform lain yang ditambahkan dalam pendaftaran aplikasi Anda, seperti Web, Anda perlu memastikan URI pengalihan tidak tumpang tindih. Lihat: Mengalihkan pembatasan URI)
  • Anda harus menyediakan polifill untuk fitur ES6 yang MSAL.js bergantung pada (misalnya, janji) untuk menjalankan aplikasi Anda di Internet Explorer
  • Memigrasikan aplikasi Microsoft Entra Anda ke titik akhir v2 jika Anda belum melakukannya

Menginstal dan mengimpor MSAL

Ada dua cara untuk menginstal pustaka MSAL.js 2.x:

Melalui npm:

npm install @azure/msal-browser

Kemudian, bergantung pada sistem modul Anda, impor seperti yang ditunjukkan di bawah ini:

import * as msal from "@azure/msal-browser"; // ESM

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

Melalui CDN:

Memuat skrip di bagian header dokumen HTML Anda:

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"></script>
  </head>
</html>

Untuk tautan CDN alternatif dan praktik terbaik saat menggunakan CDN, lihat: Penggunaan CDN

Menginisialisasi MSAL

Dalam ADAL.js, Anda membuat instans kelas AuthenticationContext , yang kemudian mengekspos metode yang dapat Anda gunakan untuk mencapai autentikasi (login, acquireTokenPopup , dan sebagainya). Objek ini berfungsi sebagai representasi koneksi aplikasi Anda ke server otorisasi atau penyedia identitas. Saat menginisialisasi, satu-satunya parameter yang wajib adalah clientId:

window.config = {
  clientId: "YOUR_CLIENT_ID"
};

var authContext = new AuthenticationContext(config);

Dalam MSAL.js, Anda membuat instans kelas PublicClientApplication. Seperti ADAL.js, konstruktor mengharapkan objek konfigurasi yang berisi setidaknya parameter clientId. Untuk mengetahui lebih lanjut, lihat: Menginisialisasi MSAL.js

const msalConfig = {
  auth: {
      clientId: 'YOUR_CLIENT_ID'
  }
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

Dalam ADAL.js dan MSAL.js, URI otoritas default https://login.microsoftonline.com/common jika Anda tidak menentukannya.

Catatan

Jika Anda menggunakan https://login.microsoftonline.com/common otoritas di v2.0, Anda akan mengizinkan pengguna untuk masuk dengan organisasi Microsoft Entra atau akun Microsoft pribadi (MSA). Di MSAL.js, jika Anda ingin membatasi login ke akun Microsoft Entra apa pun (perilaku yang sama seperti dengan ADAL.js), gunakan https://login.microsoftonline.com/organizations sebagai gantinya.

Mengonfigurasi MSAL

Beberapa opsi konfigurasi di ADAL.js yang digunakan saat menginisialisasi AuthenticationContext tidak lagi digunakan di MSAL.js, sementara beberapa yang baru diperkenalkan. Lihat daftar lengkap dari opsi yang tersedia. Yang penting, sebagian besar opsi ini, kecuali clientId, dapat diganti selama akuisisi token, sehingga Anda dapat mengaturnya berdasarkan per-permintaan. Misalnya, Anda dapat menggunakan URI otoritas atau URI pengalihan yang berbeda dari yang Anda tetapkan selama inisialisasi saat memperoleh token.

Selain itu, Anda tidak perlu lagi menentukan pengalaman login (menggunakan jendela popup ataupun mengarahkan halaman) melalui opsi konfigurasi. Sebagai gantinya, MSAL.js mengekspos metode loginPopup dan loginRedirect melalui instans PublicClientApplication.

Aktifkan pencatatan log

Di ADAL.js, Anda mengonfigurasi pengelogan secara terpisah di mana saja dalam kode Anda:

window.config = {
  clientId: "YOUR_CLIENT_ID"
};

var authContext = new AuthenticationContext(config);

var Logging = {
  level: 3,
  log: function (message) {
      console.log(message);
  },
  piiLoggingEnabled: false
};


authContext.log(Logging)

Dalam MSAL.js, pengelogan adalah bagian dari opsi konfigurasi dan dibuat selama inisialisasi PublicClientApplication:

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 msalInstance = new msal.PublicClientApplication(msalConfig);

Beralih ke API MSAL

Sebagian besar metode publik di ADAL.js memiliki ekuivalen di MSAL.js:

ADAL MSAL Catatan
acquireToken acquireTokenSilent Berganti nama dan sekarang mengharapkan objek akun
acquireTokenPopup acquireTokenPopup Sekarang asinkronkan dan mengembalikan promise
acquireTokenRedirect acquireTokenRedirect Sekarang asinkronkan dan mengembalikan promise
handleWindowCallback handleRedirectPromise Diperlukan jika menggunakan pengalaman pengalihan
getCachedUser getAllAccounts Berganti nama dan sekarang mengembalikan array akun.

Yang lain sudah tidak digunakan lagi, sementara MSAL.js menawarkan metode baru:

ADAL MSAL Catatan
login T/A Ditolak. Gunakan loginPopup atau loginRedirect
logOut T/A Ditolak. Gunakan logoutPopup atau logoutRedirect
T/A loginPopup
T/A loginRedirect
T/A logoutPopup
T/A logoutRedirect
T/A getAccountByHomeId Memfilter akun menurut ID beranda (oid + ID penyewa)
T/A getAccountLocalId Memfilter akun menurut ID lokal (berguna untuk ADFS)
T/A getAccountUsername Memfilter akun menurut nama pengguna (jika ada)

Selain itu, karena MSAL.js diterapkan dalam TypeScript, tidak seperti ADAL.js, ia mengekspos berbagai jenis dan antarmuka yang dapat Anda gunakan dalam proyek Anda. Lihat referensi API MSAL.js untuk mengetahui lebih lanjut.

Menggunakan cakupan sebagai ganti sumber daya

Perbedaan penting antara titik akhir Azure Active Directory v1.0 versus 2.0 adalah tentang bagaimana sumber daya diakses. Di ADAL.js dengan titik akhir v1.0, pertama-tama daftarkan izin di portal pendaftaran aplikasi, lalu minta token akses untuk sumber daya (seperti Microsoft Graph) seperti yang ditunjukkan di bawah ini:

authContext.acquireTokenRedirect("https://graph.microsoft.com", function (error, token) {
  // do something with the access token
});

MSAL.js hanya mendukung titik akhir v2.0 . Titik akhir v2.0 menggunakan model yang berpusat pada cakupan untuk mengakses sumber daya. Jadi, ketika Anda meminta token akses untuk sumber daya, Anda juga perlu menentukan cakupan untuk sumber daya tersebut:

msalInstance.acquireTokenRedirect({
  scopes: ["https://graph.microsoft.com/User.Read"]
});

Salah satu manfaat dari model yang berpusat pada cakupan adalah kemampuan untuk menggunakan cakupan dinamis. Saat membangun aplikasi menggunakan titik akhir v1.0, Anda harus mendaftarkan sekumpulan izin lengkap (yang disebut cakupan statis) yang diperlukan aplikasi untuk disetujui pengguna pada saat masuk. Di v2.0, Anda dapat menggunakan parameter cakupan untuk meminta izin pada waktu yang Anda inginkan (oleh karena itu, cakupan dinamis). Ini memungkinkan pengguna memberikan persetujuan bertahap untuk cakupan. Jadi, jika pada awalnya Anda hanya ingin pengguna masuk ke aplikasi Anda dan Anda tidak memerlukan akses apa pun, Anda dapat melakukannya. Jika nantinya Anda memerlukan kemampuan untuk membaca kalender pengguna, Anda selanjutnya dapat meminta cakupan kalender dalam metode acquireToken dan mendapatkan persetujuan pengguna. Lihat untuk informasi selengkapnya: Sumber daya dan cakupan

Menggunakan janji sebagai ganti panggilan balik

Di ADAL.js, panggilan balik digunakan untuk operasi apa pun setelah autentikasi berhasil dan respons diperoleh:

authContext.acquireTokenPopup(resource, extraQueryParameter, claims, function (error, token) {
  // do something with the access token
});

Di MSAL.js, promise digunakan:

msalInstance.acquireTokenPopup({
      scopes: ["User.Read"] // shorthand for https://graph.microsoft.com/User.Read
  }).then((response) => {
      // do something with the auth response
  }).catch((error) => {
      // handle errors
  });

Anda juga dapat menggunakan sintaks async/await yang disertakan dengan ES8:

const getAccessToken = async() => {
  try {
      const authResponse = await msalInstance.acquireTokenPopup({
          scopes: ["User.Read"]
      });
  } catch (error) {
      // handle errors
  }
}

Melakukan caching dan mengambil token

Seperti ADAL.js, MSAL.js token cache dan artefak autentikasi lainnya di penyimpanan browser menggunakan API Penyimpanan Web. Anda disarankan untuk menggunakan sessionStorage opsi (lihat: konfigurasi) karena lebih aman dalam menyimpan token yang diperoleh oleh pengguna Anda, tetapi localStorage akan memberi Anda Akses Menyeluruh di seluruh tab dan sesi pengguna.

Yang penting, Anda tidak seharusnya mengakses cache secara langsung. Anda seharusnya menggunakan API MSAL.js yang sesuai untuk mengambil artefak autentikasi seperti token akses atau akun pengguna.

Memperbarui token dengan token refresh

ADAL.js menggunakan alur implisit OAuth 2.0, yang tidak mengembalikan token refresh karena alasan keamanan (token refresh memiliki masa pakai yang lebih lama daripada token akses dan oleh karena itu lebih berbahaya di tangan pelaku jahat). Oleh karena itu, ADAL.js melakukan perpanjangan token menggunakan IFrame tersembunyi sehingga pengguna tidak berulang kali diminta untuk mengautentikasi.

Dengan alur kode autentikasi dengan dukungan PKCE, aplikasi yang menggunakan MSAL.js 2.x mendapatkan token refresh bersama dengan token ID dan akses, yang dapat digunakan untuk memperbaruinya. Penggunaan token refresh diabstraksi, dan pengembang tidak seharusnya membangun logika di sekitarnya. Sebagai gantinya, MSAL mengelola perpanjangan token menggunakan token refresh dengan sendirinya. Cache token Anda sebelumnya dengan ADAL.js tidak akan dapat ditransfer ke MSAL.js, karena skema cache token telah berubah dan tidak kompatibel dengan skema yang digunakan dalam ADAL.js.

Menangani kesalahan umum dan pengecualian

Saat menggunakan MSAL.js, jenis kesalahan paling umum yang mungkin Anda hadapi adalah kesalahan interaction_in_progress. Kesalahan ini dilemparkan ketika API interaktif (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect) dipanggil, sementara API interaktif lain masih berlangsung. login* API dan acquireToken* adalah asinkron sehingga Anda harus memastikan bahwa janji yang dihasilkan telah diselesaikan sebelum memanggil yang lain.

Kesalahan umum lainnya adalah interaction_required. Kesalahan ini sering diatasi dengan memulai permintaan akuisisi token interaktif. Misalnya, API web yang coba Anda akses mungkin memiliki kebijakan Akses Bersyarat, mengharuskan pengguna untuk melakukan autentikasi multifaktor (MFA). Dalam hal ini, menangani kesalahan interaction_required dengan memicu acquireTokenPopup atau acquireTokenRedirect akan menghasilkan perintah pengguna untuk MFA, memungkinkan mereka untuk memenuhinya.

Namun kesalahan umum lain yang mungkin Anda hadapi adalah consent_required, yang terjadi ketika izin yang diperlukan untuk mendapatkan token akses untuk sumber daya yang dilindungi tidak disetujui oleh pengguna. Seperti pada interaction_required, solusi untuk kesalahan consent_required sering kali memulai permintaan akuisisi token interaktif, baik menggunakan acquireTokenPopup ataupun acquireTokenRedirect.

Untuk mengetahui lebih lanjut, lihat: Kesalahan umum MSAL.js dan cara menanganinya

Menggunakan Kejadian API

MSAL.js (>=v2.4) memperkenalkan API peristiwa yang dapat Anda gunakan dalam aplikasi. Peristiwa ini terkait dengan proses autentikasi dan hal yang dilakukan MSAL di setiap waktu, dan dapat digunakan untuk memperbarui UI, menampilkan pesan kesalahan, memeriksa interaksi yang sedang berlangsung dan sebagainya. Misalnya, berikut adalah panggilan balik kejadian yang akan dipanggil ketika proses masuk gagal karena alasan apa pun:

const callbackId = msalInstance.addEventCallback((message) => {
  // Update UI or interact with EventMessage here
  if (message.eventType === EventType.LOGIN_FAILURE) {
      if (message.error instanceof AuthError) {
          // Do something with the error
      }
    }
});

Untuk performa, penting untuk membatalkan pendaftaran panggilan balik peristiwa saat tidak lagi diperlukan. Untuk mengetahui lebih lanjut, lihat: API Kejadian MSAL.js

Menangani beberapa akun

ADAL.js memiliki konsep pengguna untuk mewakili entitas yang saat ini diautentikasi. MSAL.js mengganti pengguna dengan akun, mengingat fakta bahwa pengguna dapat memiliki lebih dari satu akun yang terkait dengan mereka. Hal ini juga berarti bahwa Anda sekarang perlu mengontrol beberapa akun dan memilih yang sesuai untuk digunakan. Cuplikan berikut menggambarkan proses ini:

let homeAccountId = null; // Initialize global accountId (can also be localAccountId or username) used for account lookup later, ideally stored in app state

// This callback is passed into `acquireTokenPopup` and `acquireTokenRedirect` to handle the interactive auth response
function handleResponse(resp) {
  if (resp !== null) {
      homeAccountId = resp.account.homeAccountId; // alternatively: resp.account.homeAccountId or resp.account.username
  } else {
      const currentAccounts = myMSALObj.getAllAccounts();
      if (currentAccounts.length < 1) { // No cached accounts
          return;
      } else if (currentAccounts.length > 1) { // Multiple account scenario
          // Add account selection logic here
      } else if (currentAccounts.length === 1) {
          homeAccountId = currentAccounts[0].homeAccountId; // Single account scenario
      }
  }
}

Untuk informasi lebih lanjut, lihat: Akun di MSAL.js

Menggunakan pustaka pembungkus

Jika Anda mengembangkan kerangka kerja Angular dan React, Anda dapat menggunakan MSAL Angular v2 dan MSAL React. Pembungkus ini mengekspos API publik yang sama dengan MSAL.js sambil menawarkan metode dan komponen khusus kerangka kerja yang dapat mempermudah proses autentikasi dan akuisisi token.

Menjalankan aplikasi

Setelah perubahan selesai, jalankan aplikasi dan uji skenario autentikasi Anda:

npm start

Contoh: Mengamankan SPA dengan ADAL.js vs. MSAL.js

Cuplikan di bawah ini menunjukkan kode minimal yang diperlukan untuk aplikasi satu halaman yang mengautentikasi pengguna dengan platform identitas Microsoft dan mendapatkan token akses untuk Microsoft Graph menggunakan ADAL.js terlebih dahulu dan kemudian MSAL.js:

Menggunakan ADAL.js Menggunakan MSAL.js

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.0.18/js/adal.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        var welcomeMessage = document.getElementById("welcomeMessage");
        var loginButton = document.getElementById("loginButton");
        var logoutButton = document.getElementById("logoutButton");
        var tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        function updateUI(user) {
            if (!user) {
                return;
            }

            welcomeMessage.innerHTML = 'Hello ' + user.profile.upn + '!';
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // attach logger configuration to window
        window.Logging = {
            piiLoggingEnabled: false,
            level: 3,
            log: function (message) {
                console.log(message);
            }
        };

        // ADAL configuration
        var adalConfig = {
            instance: 'https://login.microsoftonline.com/',
            clientId: "ENTER_CLIENT_ID_HERE",
            tenant: "ENTER_TENANT_ID_HERE",
            redirectUri: "ENTER_REDIRECT_URI_HERE",
            cacheLocation: "sessionStorage",
            popUp: true,
            callback: function (errorDesc, token, error, tokenType) {
                if (error) {
                    console.log(error, errorDesc);
                } else {
                    updateUI(authContext.getCachedUser());
                }
            }
        };

        // instantiate ADAL client object
        var authContext = new AuthenticationContext(adalConfig);

        // handle redirect response or check for cached user
        if (authContext.isCallback(window.location.hash)) {
            authContext.handleWindowCallback();
        } else {
            updateUI(authContext.getCachedUser());
        }

        // attach event handlers to button clicks
        loginButton.addEventListener('click', function () {
            authContext.login();
        });

        logoutButton.addEventListener('click', function () {
            authContext.logOut();
        });

        tokenButton.addEventListener('click', () => {
            authContext.acquireToken(
                "https://graph.microsoft.com",
                function (errorDesc, token, error) {
                    if (error) {
                        console.log(error, errorDesc);

                        authContext.acquireTokenPopup(
                            "https://graph.microsoft.com",
                            null, // extraQueryParameters
                            null, // claims
                            function (errorDesc, token, error) {
                                if (error) {
                                    console.log(error, errorDesc);
                                } else {
                                    console.log(token);
                                }
                            }
                        );
                    } else {
                        console.log(token);
                    }
                }
            );
        });
    </script>
</body>

</html>


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.34.0/js/msal-browser.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        const welcomeMessage = document.getElementById("welcomeMessage");
        const loginButton = document.getElementById("loginButton");
        const logoutButton = document.getElementById("logoutButton");
        const tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        const updateUI = (account) => {
            if (!account) {
                return;
            }

            welcomeMessage.innerHTML = `Hello ${account.username}!`;
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // MSAL configuration
        const msalConfig = {
            auth: {
                clientId: "ENTER_CLIENT_ID_HERE",
                authority: "https://login.microsoftonline.com/ENTER_TENANT_ID_HERE",
                redirectUri: "ENTER_REDIRECT_URI_HERE",
            },
            cache: {
                cacheLocation: "sessionStorage"
            },
            system: {
                loggerOptions: {
                    loggerCallback(loglevel, message, containsPii) {
                        console.log(message);
                    },
                    piiLoggingEnabled: false,
                    logLevel: msal.LogLevel.Verbose,
                }
            }
        };

        // instantiate MSAL client object
        const pca = new msal.PublicClientApplication(msalConfig);

        // handle redirect response or check for cached user
        pca.handleRedirectPromise().then((response) => {
            if (response) {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            } else {
                const account = pca.getAllAccounts()[0];
                updateUI(account);
            }
        }).catch((error) => {
            console.log(error);
        });

        // attach event handlers to button clicks
        loginButton.addEventListener('click', () => {
            pca.loginPopup().then((response) => {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            })
        });

        logoutButton.addEventListener('click', () => {
            pca.logoutPopup().then((response) => {
                window.location.reload();
            });
        });

        tokenButton.addEventListener('click', () => {
            const account = pca.getActiveAccount();

            pca.acquireTokenSilent({
                account: account,
                scopes: ["User.Read"]
            }).then((response) => {
                console.log(response);
            }).catch((error) => {
                if (error instanceof msal.InteractionRequiredAuthError) {
                    pca.acquireTokenPopup({
                        scopes: ["User.Read"]
                    }).then((response) => {
                        console.log(response);
                    });
                }

                console.log(error);
            });
        });
    </script>
</body>

</html>

Langkah berikutnya