تمكين المصادقة في تطبيق الصفحة الواحدة الخاص بك باستخدام Azure AD B2C

توضح هذه المقالة كيفية إضافة مصادقة Azure Active Directory B2C (Azure AD B2C) إلى تطبيق الصفحة الواحدة (SPA) الخاص بك. تعرف على كيفية تهيئة إنشاء تطبيق SPA باستخدام مكتبة مصادقة Microsoft لـ JavaScript (MSAL.js).

استخدم هذه المقالة مع تكوين المصادقة في عينة تطبيق SPA، استبدال عينة تطبيق SPA مع تطبيق SPA الخاصة بك.

نظرة عامة

تستخدم هذه المقالة Node.js وExpress لإنشاء تطبيق ويب Node.js أساسي. إن Express هو إطار عمل لتطبيق ويب Node.js مبسط ومرن يوفر مجموعة من الميزات لتطبيقات الويب والهاتف المحمول.

إن MSAL.js هي مكتبة توفرها Microsoft تعمل على تبسيط إضافة دعم المصادقة والتخويل إلى تطبيقات SPA.

تلميح

يتم تشغيل التعليمات البرمجية لـ MSAL.js بالكامل على جانب العميل. يمكنك استبدال Node.js والتعليمة البرمجية لجانب خادم Express بحلول أخرى، مثل لغات البرمجة النصية .NET Core وJava وHypertext Preprocessor (PHP).

المتطلبات الأساسية

لمراجعة المتطلبات الأساسية وإرشادات التكامل، راجع تكوين المصادقة في نموذج تطبيق SPA.

الخطوة 1: إنشاء مشروع تطبيق SPA

يمكنك استخدام مشروع تطبيق SPA موجود أو إنشاء مشروع جديد. لإنشاء مشروع جديد، نفذ ما يلي:

  1. قم بفتح shell لأمر وإنشاء دليل جديد (على سبيل المثال، myApp). سيحتوي هذا الدليل على التعليمة البرمجية وواجهة المستخدم وملفات التكوين للتطبيق الخاص بك.

  2. أدخل الدليل الذي أنشأته.

  3. استخدم أمر npm init لإنشاء ملف package.json لتطبيقك. يطالبك هذا الأمر بمعلومات حول التطبيق الخاص بك (على سبيل المثال، اسم التطبيق وإصداره واسم نقطة الإدخال الأولية، وملف index.js). قم بتشغيل الأمر التالي، وقبول القيم الافتراضية:

npm init

الخطوة 2: تثبيت التبعيات

لتثبيت حزمة Express في shell الأمر، قم بتشغيل الأمر التالي:

npm install express

لتحديد موقع الملفات الثابتة الخاصة بالتطبيق، تستخدم التعليمات البرمجية من جانب الخادم حزمة المسار.

لتثبيت حزمة المسار في shell الأمر الخاص بك، قم بتشغيل الأمر التالي:

npm install path

الخطوة 3: تكوين خادم الويب الخاص بك

في مجلد myApp الخاص بك، قم بإنشاء ملف يُسمى index.js، بحيث يحتوي على التعليمة البرمجية التالية:

// Initialize express
const express = require('express');
const app = express();

// The port to listen to incoming HTTP requests
const port = 6420;

// Initialize path
const path = require('path');

// Set the front-end folder to serve public assets.
app.use(express.static('App'));

// Set up a route for the index.html
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html'));
});

// Start the server, and listen for HTTP requests
app.listen(port, () => {
  console.log(`Listening on http://localhost:${port}`);
});

الخطوة 4: إنشاء واجهة المستخدم SPA

أضف ملف تطبيق index.html SPA. هذا الملف ينفذ واجهة المستخدم التي تم إنشاؤها مع إطار عمل نظام تمهيد تشغيل الكمبيوتر، ويتم استيراد ملفات البرنامج النصي للتكوين والمصادقة، واستدعاءات واجهة برمجة تطبيقات الويب.

الموارد المشار إليها في ملف index.html مفصلة في الجدول التالي:

‏‏المرجع التعريف
مكتبة التعليمات البرمجية MSAL.js مكتبة التعليمات البرمجية JavaScript لمصادقة MSAL.js مسار شبكة تسليم المحتوى.
ورقة أنماط نظام تمهيد تشغيل الكمبيوتر إطار عمل للواجهة الأمامية مجاني لتطوير الويب بشكل أسرع وأسهل. يتضمن إطار العمل قوالب تصميم تستند إلى HTML وCSS.
policies.js يحتوي على نُهج Azure AD B2C المخصصة وتدفقات المستخدم.
authConfig.js يحتوي على معلمات تكوين المصادقة.
authRedirect.js يحتوي على منطق المصادقة.
apiConfig.js يحتوي على نطاقات واجهة برمجة تطبيقات الويب وموقع نقطة نهاية واجهة برمجة التطبيقات.
api.js تعريف الأسلوب الذي يجب استخدامه لاستدعاء واجهة برمجة التطبيقات ومعالجة استجابتها.
ui.js يتحكم في عناصر واجهة المستخدم.

لعرض ملف فهرس SPA، في مجلد myApp ، قم بإنشاء ملف يسمى index.html، والذي يحتوي على مقتطف HTML التالي:

<!DOCTYPE html>
<html>
    <head>
        <title>My Azure AD B2C test app</title>
    </head>
    <body>
        <h2>My Azure AD B2C test app</h2>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />
        <button type="button" id="signIn" class="btn btn-secondary" onclick="signIn()">Sign-in</button>
        <button type="button" id="signOut" class="btn btn-success d-none" onclick="signOut()">Sign-out</button>
        <h5 id="welcome-div" class="card-header text-center d-none"></h5>
        <br />
        <!-- Content -->
        <div class="card">
            <div class="card-body text-center">
                <pre id="response" class="card-text"></pre>
                <button type="button" id="callApiButton" class="btn btn-primary d-none" onclick="passTokenToApi()">Call API</button>
            </div>
        </div>
        <script src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js" integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr" crossorigin="anonymous"></script>

        <!-- Importing app scripts (load order is important) -->
        <script type="text/javascript" src="./apiConfig.js"></script>
        <script type="text/javascript" src="./policies.js"></script>
        <script type="text/javascript" src="./authConfig.js"></script>
        <script type="text/javascript" src="./ui.js"></script>

        <!-- <script type="text/javascript" src="./authRedirect.js"></script>   -->
        <!-- uncomment the above line and comment the line below if you would like to use the redirect flow -->
        <script type="text/javascript" src="./authRedirect.js"></script>
        <script type="text/javascript" src="./api.js"></script>
    </body>
</html>

الخطوة 5: تكوين مكتبة التعليمات البرمجية المصادقة

تكوين كيفية تكامل مكتبة التعليمات البرمجية MSAL.js مع Azure AD B2C. تستخدم مكتبة MSAL.js عنصر تكوين شائع للاتصال بنقاط نهاية مصادقة مستأجر Azure AD B2C الخاص بك.

لتكوين مكتبة التعليمات البرمجية للمصادقة، قم بما يلي:

  1. في مجلد myApp، قم بإنشاء مجلد جديد يسمى App.

  2. داخل مجلد App، أنشئ ملف جديد يسمى authConfig.js.

  3. أضف التعليمة البرمجية JavaScript التالية إلى ملف authConfig.js:

    const msalConfig = {
        auth: {
        clientId: "<Application-ID>", 
        authority: b2cPolicies.authorities.signUpSignIn.authority, 
        knownAuthorities: [b2cPolicies.authorityDomain], 
        redirectUri: "http://localhost:6420",
        },
        cache: {
        cacheLocation: "localStorage", .
        storeAuthStateInCookie: false, 
        }
    };
    
    const loginRequest = {
    scopes: ["openid", ...apiConfig.b2cScopes],
    };
    
    const tokenRequest = {
    scopes: [...apiConfig.b2cScopes],
    forceRefresh: false
    };
    
  4. استبدل <Application-ID> بمعرف تسجيل التطبيق الخاص بالتطبيق الخاص بك. للحصول على مزيدٍ من المعلومات، راجع تكوين المصادقة في عينة تطبيق SPA.

تلميح

لمزيد من خيارات تكوين عنصر MSAL، راجع مقالة خيارات المصادقة.

الخطوة 6: تحديد تدفق المستخدم في Azure AD B2C

إنشاء ملف policies.js، الذي يوفر معلومات حول بيئة Azure AD B2C. تستخدم مكتبة التعليمات البرمجية MSAL.js هذه المعلومات لإنشاء طلبات مصادقة إلى Azure AD B2C.

لتحديد تدفق المستخدم في Azure AD B2C، قم بما يلي:

  1. داخل مجلد App، أنشئ ملف جديد باسم policies.js.

  2. أضف التعليمة البرمجية الآتية إلى ملف policies.js:

    const b2cPolicies = {
        names: {
            signUpSignIn: "B2C_1_SUSI",
            editProfile: "B2C_1_EditProfile"
        },
        authorities: {
            signUpSignIn: {
                authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-SignInOrSignUp-Policy-Id",
            },
            editProfile: {
                authority: "https://contoso.b2clogin.com/contoso.onmicrosoft.com/Your-B2C-EditProfile-Policy-Id"
            }
        },
        authorityDomain: "contoso.b2clogin.com"
    }
    
  3. استبدل B2C_1_SUSI باسم نهج Azure AD B2C لتسجيل الدخول الخاص بك.

  4. استبدل B2C_1_EditProfile باسم نهج Azure AD B2C لتحرير ملف التعريف الخاص بك.

  5. استبدل كافة مثيلات contosoباسم مستأجر Azure AD B2C.

الخطوة 7: استخدام MSAL لتسجيل دخول المستخدم

في هذه الخطوة، قم بتطبيق الأساليب لتهيئة تدفق تسجيل الدخول، والحصول على الرمز المميز للوصول إلى واجهة برمجة التطبيقات، وأساليب تسجيل الخروج.

لمزيد من المعلومات، راجع مقالة استخدام مكتبة مصادقة Microsoft (MSAL) لتسجيل الدخول إلى المستخدم .

لتسجيل الدخول للمستخدم، قم بما يلي:

  1. داخل مجلد App، أنشئ ملف جديد باسم authRedirect.js.

  2. في authRedirect.js الخاص بك، قم بنسخ التعليمة البرمجية التالية ولصقها:

    // Create the main myMSALObj instance
    // configuration parameters are located at authConfig.js
    const myMSALObj = new msal.PublicClientApplication(msalConfig);
    
    let accountId = "";
    let idTokenObject = "";
    let accessToken = null;
    
    myMSALObj.handleRedirectPromise()
        .then(response => {
            if (response) {
                /**
                 * For the purpose of setting an active account for UI update, we want to consider only the auth response resulting
                 * from SUSI flow. "tfp" claim in the id token tells us the policy (NOTE: legacy policies may use "acr" instead of "tfp").
                 * To learn more about B2C tokens, visit https://learn.microsoft.com/azure/active-directory-b2c/tokens-overview
                 */
                if (response.idTokenClaims['tfp'].toUpperCase() === b2cPolicies.names.signUpSignIn.toUpperCase()) {
                    handleResponse(response);
                }
            }
        })
        .catch(error => {
            console.log(error);
        });
    
    
    function setAccount(account) {
        accountId = account.homeAccountId;
        idTokenObject = account.idTokenClaims;
        myClaims= JSON.stringify(idTokenObject);
        welcomeUser(myClaims);
    }
    
    function selectAccount() {
    
        /**
         * See here for more information on account retrieval: 
         * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
         */
    
        const currentAccounts = myMSALObj.getAllAccounts();
    
        if (currentAccounts.length < 1) {
            return;
        } else if (currentAccounts.length > 1) {
    
            /**
             * Due to the way MSAL caches account objects, the auth response from initiating a user-flow
             * is cached as a new account, which results in more than one account in the cache. Here we make
             * sure we are selecting the account with homeAccountId that contains the sign-up/sign-in user-flow, 
             * as this is the default flow the user initially signed-in with.
             */
            const accounts = currentAccounts.filter(account =>
                account.homeAccountId.toUpperCase().includes(b2cPolicies.names.signUpSignIn.toUpperCase())
                &&
                account.idTokenClaims.iss.toUpperCase().includes(b2cPolicies.authorityDomain.toUpperCase())
                &&
                account.idTokenClaims.aud === msalConfig.auth.clientId 
                );
    
            if (accounts.length > 1) {
                // localAccountId identifies the entity for which the token asserts information.
                if (accounts.every(account => account.localAccountId === accounts[0].localAccountId)) {
                    // All accounts belong to the same user
                    setAccount(accounts[0]);
                } else {
                    // Multiple users detected. Logout all to be safe.
                    signOut();
                };
            } else if (accounts.length === 1) {
                setAccount(accounts[0]);
            }
    
        } else if (currentAccounts.length === 1) {
            setAccount(currentAccounts[0]);
        }
    }
    
    // in case of page refresh
    selectAccount();
    
    async function handleResponse(response) {
    
        if (response !== null) {
            setAccount(response.account);
        } else {
            selectAccount();
        }
    }
    
    function signIn() {
        myMSALObj.loginRedirect(loginRequest);
    }
    
    function signOut() {
        const logoutRequest = {
            postLogoutRedirectUri: msalConfig.auth.redirectUri,
        };
    
        myMSALObj.logoutRedirect(logoutRequest);
    }
    
    function getTokenRedirect(request) {
        request.account = myMSALObj.getAccountByHomeId(accountId); 
    
        return myMSALObj.acquireTokenSilent(request)
            .then((response) => {
                // In case the response from B2C server has an empty accessToken field
                // throw an error to initiate token acquisition
                if (!response.accessToken || response.accessToken === "") {
                    throw new msal.InteractionRequiredAuthError;
                } else {
                    console.log("access_token acquired at: " + new Date().toString());
                    accessToken = response.accessToken;
                    passTokenToApi();
                }
            }).catch(error => {
                console.log("Silent token acquisition fails. Acquiring token using popup. \n", error);
                if (error instanceof msal.InteractionRequiredAuthError) {
                    // fallback to interaction when silent call fails
                    return myMSALObj.acquireTokenRedirect(request);
                } else {
                    console.log(error);   
                }
        });
    }
    
    // Acquires and access token and then passes it to the API call
    function passTokenToApi() {
        if (!accessToken) {
            getTokenRedirect(tokenRequest);
        } else {
            try {
                callApi(apiConfig.webApi, accessToken);
            } catch(error) {
                console.log(error); 
            }
        }
    }
    
    function editProfile() {
    
    
        const editProfileRequest = b2cPolicies.authorities.editProfile;
        editProfileRequest.loginHint = myMSALObj.getAccountByHomeId(accountId).username;
    
        myMSALObj.loginRedirect(editProfileRequest);
    }
    

الخطوة 8: تكوين موقع واجهة برمجة تطبيقات الويب ونطاقه

للسماح لتطبيق SPA الخاص بك باستدعاء واجهة برمجة تطبيقات الويب، قم بتوفير موقع نقطة نهاية واجهة برمجة تطبيقات الويب والنطاقات التي يجب استخدامها لتخويل الوصول إلى واجهة برمجة تطبيقات الويب.

لتكوين موقع واجهة برمجة تطبيقات الويب والنطاقات، قم بما يلي:

  1. داخل مجلد App، أنشئ ملف جديد باسم apiConfig.js.

  2. في apiConfig.js الخاص بك، قم بنسخ التعليمة البرمجية التالية ولصقها:

    // The current application coordinates were pre-registered in a B2C tenant.
    const apiConfig = {
        b2cScopes: ["https://contoso.onmicrosoft.com/tasks/tasks.read"],
        webApi: "https://mydomain.azurewebsites.net/tasks"
    };
    
  3. استبدل contoso باسم المستأجر الخاص بك. يمكن العثور على اسم النطاق المطلوب كما هو موضح في مقالة تكوين النطاقات.

  4. استبدل قيمة webApi بموقع نقطة نهاية واجهة برمجة تطبيقات الويب.

الخطوة 9: استدعاء واجهة برمجة تطبيقات الويب الخاصة بك

تعريف طلب HTTP إلى نقطة نهاية واجهة برمجة التطبيقات الخاصة بك. تم تكوين طلب HTTP لتمرير الرمز المميز للوصول الذي تم الحصول عليه مع MSAL.js في عنوان HTTP Authorization في الطلب.

تعرف التعليمة البرمجية التالية طلب HTTP GET إلى نقطة نهاية واجهة برمجة التطبيقات، وتمرير الرمز المميز للوصول ضمن عنوان HTTP Authorization. يتم تعريف موقع واجهة برمجة التطبيقات بواسطة مفتاح webApi في apiConfig.js.

لاستدعاء واجهة برمجة تطبيقات الويب باستخدام الرمز المميز الذي حصلت عليه، قم بما يلي:

  1. داخل مجلد App، أنشئ ملف جديد باسم api.js.

  2. أضف التعليمة البرمجية التالية إلى ملف api.js:

    function callApi(endpoint, token) {
    
        const headers = new Headers();
        const bearer = `Bearer ${token}`;
    
        headers.append("Authorization", bearer);
    
        const options = {
            method: "GET",
            headers: headers
        };
    
        logMessage('Calling web API...');
    
        fetch(endpoint, options)
        .then(response => response.json())
        .then(response => {
    
            if (response) {
            logMessage('Web API responded: ' + response.name);
            }
    
            return response;
        }).catch(error => {
            console.error(error);
        });
    }
    

الخطوة 10: أضف مرجع عناصر واجهة المستخدم

يستخدم تطبيق SPA JavaScript للتحكم في عناصر واجهة المستخدم. على سبيل المثال، يعرض زري تسجيل الدخول والخروج، ويعرض مطالبات الرمز المميز لمعرف المستخدمين على الشاشة.

لإضافة مرجع عناصر واجهة المستخدم، قم بما يلي:

  1. داخل مجلد App، أنشئ ملف جديد باسم ui.js.

  2. أضف التعليمة البرمجية التالية إلى ملف ui.js:

    // Select DOM elements to work with
    const signInButton = document.getElementById('signIn');
    const signOutButton = document.getElementById('signOut')
    const titleDiv = document.getElementById('title-div');
    const welcomeDiv = document.getElementById('welcome-div');
    const tableDiv = document.getElementById('table-div');
    const tableBody = document.getElementById('table-body-div');
    const editProfileButton = document.getElementById('editProfileButton');
    const callApiButton = document.getElementById('callApiButton');
    const response = document.getElementById("response");
    const label = document.getElementById('label');
    
    function welcomeUser(claims) {
        welcomeDiv.innerHTML = `Token claims: </br></br> ${claims}!`
    
        signInButton.classList.add('d-none');
        signOutButton.classList.remove('d-none');
        welcomeDiv.classList.remove('d-none');
        callApiButton.classList.remove('d-none');
    }
    
    function logMessage(s) {
        response.appendChild(document.createTextNode('\n' + s + '\n'));
    }
    

الخطوة 11: قم بتشغيل تطبيق SPA الخاص بك

في shell الأمر الخاص بك، قم بتشغيل الأوامر التالية:

npm install  
npm ./index.js
  1. انتقل إلى https://localhost:6420.
  2. حددSign-in.
  3. أكمل عملية التسجيل أو تسجيل الدخول.

بعد المصادقة بنجاح، يتم عرض الرمز المميز للمعرف الموزع على الشاشة. حدد Call API لاستدعاء نقطة نهاية واجهة برمجة التطبيقات الخاصة بك.

الخطوات التالية