Bagikan melalui


Mengedit profil di aplikasi web Node.js

Berlaku untuk: Lingkaran putih dengan simbol X abu-abu. Penyewa Tenaga Kerja Penyewa Lingkaran hijau dengan simbol tanda centang putih. eksternal (pelajari lebih lanjut)

Artikel ini adalah bagian 2 dari seri yang menunjukkan cara menambahkan logika pengeditan profil di aplikasi web Node.js. Di bagian 1 seri ini, Anda menyiapkan aplikasi untuk pengeditan profil.

Dalam panduan cara ini, Anda mempelajari cara memanggil Microsoft Graph API untuk pengeditan profil.

Prasyarat

Menyelesaikan aplikasi web klien

Di bagian ini, Anda menambahkan kode terkait identitas untuk aplikasi web klien.

Memperbarui file authConfig.js

Perbarui file authConfig.js untuk aplikasi web klien:

  1. Di editor kode Anda, buka file App/authConfig.js , lalu tambahkan tiga variabel baru, GRAPH_API_ENDPOINT, GRAPH_ME_ENDPOINT dan editProfileScope. Pastikan untuk mengekspor tiga variabel:

    //...
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "https://graph.microsoft.com/";
    // https://learn.microsoft.com/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    const editProfileScope = process.env.EDIT_PROFILE_FOR_CLIENT_WEB_APP || 'api://{clientId}/EditProfileService.ReadWrite';
    
    module.exports = {
        //...
        editProfileScope,
        GRAPH_API_ENDPOINT,
        GRAPH_ME_ENDPOINT,
        //...
    };
    
    • Variabel editProfileScope mewakili sumber daya yang dilindungi MFA, yaitu aplikasi tingkat menengah (aplikasi EditProfileService).

    • GRAPH_ME_ENDPOINT adalah titik akhir Microsoft Graph API.

  2. Ganti tempat penampung {clientId} dengan ID Aplikasi (klien) dari aplikasi tingkat menengah (aplikasi EditProfileService) yang Anda daftarkan sebelumnya.

Memperoleh token akses di aplikasi web klien

Di editor kode Anda, buka file App/auth/AuthProvider.js , lalu perbarui getToken metode di AuthProvider kelas :

    class AuthProvider {
    //...
        getToken(scopes, redirectUri = "http://localhost:3000/") {
            return  async function (req, res, next) {
                const msalInstance = authProvider.getMsalInstance(authProvider.config.msalConfig);
                try {
                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
    
                    const silentRequest = {
                        account: req.session.account,
                        scopes: scopes,
                    };
                    const tokenResponse = await msalInstance.acquireTokenSilent(silentRequest);
    
                    req.session.tokenCache = msalInstance.getTokenCache().serialize();
                    req.session.accessToken = tokenResponse.accessToken;
                    next();
                } catch (error) {
                    if (error instanceof msal.InteractionRequiredAuthError) {
                        req.session.csrfToken = authProvider.cryptoProvider.createNewGuid();
    
                        const state = authProvider.cryptoProvider.base64Encode(
                            JSON.stringify({
                                redirectTo: redirectUri,
                                csrfToken: req.session.csrfToken,
                            })
                        );
                        
                        const authCodeUrlRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        const authCodeRequestParams = {
                            state: state,
                            scopes: scopes,
                        };
    
                        authProvider.redirectToAuthCodeUrl(
                            req,
                            res,
                            next,
                            authCodeUrlRequestParams,
                            authCodeRequestParams,
                            msalInstance
                        );
                    }
    
                    next(error);
                }
            };
        }
    }
    //...

Metode ini getToken menggunakan cakupan yang ditentukan untuk memperoleh token akses. Parameter redirectUri adalah URL pengalihan setelah aplikasi memperoleh token akses.

Memperbarui file users.js

Di editor kode Anda, buka file App/routes/users.js , lalu tambahkan rute berikut:

    //...
    
    var { fetch } = require("../fetch");
    const { GRAPH_ME_ENDPOINT, editProfileScope } = require('../authConfig');
    //...
    
router.get(
  "/gatedUpdateProfile",
  isAuthenticated,
  authProvider.getToken(["User.Read"]), // check if user is authenticated
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("gatedUpdateProfile", {
      profile: graphResponse,
    });
  },
);

router.get(
  "/updateProfile",
  isAuthenticated, // check if user is authenticated
  authProvider.getToken(
    ["User.Read", editProfileScope],
    "http://localhost:3000/users/updateProfile",
  ),
  async function (req, res, next) {
    const graphResponse = await fetch(
      GRAPH_ME_ENDPOINT,
      req.session.accessToken,
    );
    if (!graphResponse.id) {
      return res 
        .status(501) 
        .send("Failed to fetch profile data"); 
    }
    res.render("updateProfile", {
      profile: graphResponse,
    });
  },
);

router.post(
  "/update",
  isAuthenticated,
  authProvider.getToken([editProfileScope]),
  async function (req, res, next) {
    try {
      if (!!req.body) {
        let body = req.body;
        fetch(
          "http://localhost:3001/updateUserInfo",
          req.session.accessToken,
          "POST",
          {
            displayName: body.displayName,
            givenName: body.givenName,
            surname: body.surname,
          },
        )
          .then((response) => {
            if (response.status === 204) {
              return res.redirect("/");
            } else {
              next("Not updated");
            }
          })
          .catch((error) => {
            console.log("error,", error);
          });
      } else {
        throw { error: "empty request" };
      }
    } catch (error) {
      next(error);
    }
  },
);
    //...
  • Anda memicu /gatedUpdateProfile rute saat pengguna pelanggan memilih tautan Pengeditan profil. Aplikasi:

    1. Memperoleh token akses dengan izin User.Read .
    2. Lakukan panggilan ke Microsoft Graph API untuk membaca profil pengguna yang masuk.
    3. Menampilkan detail pengguna di UI gatedUpdateProfile.hbs .
  • Anda memicu /updateProfile rute saat pengguna ingin memperbarui nama tampilan mereka, yaitu, mereka memilih tombol Edit profil . Aplikasi:

    1. Melakukan panggilan ke aplikasi tingkat menengah (aplikasi EditProfileService) menggunakan cakupan editProfileScope . Dengan melakukan panggilan ke aplikasi tingkat menengah (aplikasi EditProfileService), pengguna harus menyelesaikan tantangan MFA jika mereka belum melakukannya.
    2. Menampilkan detail pengguna di antarmuka pengguna updateProfile.hbs .
  • Anda memicu /update rute saat pengguna memilih tombol Simpan di gatedUpdateProfile.hbs atau updateProfile.hbs. Aplikasi:

    1. Mengambil token akses untuk sesi aplikasi. Anda mempelajari bagaimana aplikasi tingkat menengah (aplikasi EditProfileService) memperoleh token akses di bagian berikutnya.
    2. Mengumpulkan semua detail pengguna.
    3. Lakukan panggilan ke Microsoft Graph API untuk memperbarui profil pengguna.

Memperbarui file fetch.js

Aplikasi ini menggunakan file App/fetch.js untuk melakukan panggilan API aktual.

Di editor kode Anda, buka file App/fetch.js , lalu tambahkan opsi operasi PATCH. Setelah Anda memperbarui file, file yang dihasilkan akan terlihat mirip dengan kode berikut:

var axios = require('axios');
var authProvider = require("./auth/AuthProvider");

/**
 * Makes an Authorization "Bearer" request with the given accessToken to the given endpoint.
 * @param endpoint
 * @param accessToken
 * @param method
 */
const fetch = async (endpoint, accessToken, method = "GET", data = null) => {
    const options = {
        headers: {
            Authorization: `Bearer ${accessToken}`,
        },
    };
    console.log(`request made to ${endpoint} at: ` + new Date().toString());

    switch (method) {
        case 'GET':
            const response = await axios.get(endpoint, options);
            return await response.data;
        case 'POST':
            return await axios.post(endpoint, data, options);
        case 'DELETE':
            return await axios.delete(endpoint + `/${data}`, options);
        case 'PATCH': 
            return await axios.patch(endpoint, ReqBody = data, options);
        default:
            return null;
    }
};

module.exports = { fetch };

Menyelesaikan aplikasi tingkat menengah

Di bagian ini, Anda menambahkan kode terkait identitas untuk aplikasi tingkat menengah (aplikasi EditProfileService).

  1. Di editor kode Anda, buka file Api/authConfig.js , lalu tambahkan kode berikut:

    require("dotenv").config({ path: ".env.dev" });
    
    const TENANT_SUBDOMAIN =
      process.env.TENANT_SUBDOMAIN || "Enter_the_Tenant_Subdomain_Here";
    const TENANT_ID = process.env.TENANT_ID || "Enter_the_Tenant_ID_Here";
    const REDIRECT_URI =
      process.env.REDIRECT_URI || "http://localhost:3000/auth/redirect";
    const POST_LOGOUT_REDIRECT_URI =
      process.env.POST_LOGOUT_REDIRECT_URI || "http://localhost:3000";
    
    /**
     * Configuration object to be passed to MSAL instance on creation.
     * For a full list of MSAL Node configuration parameters, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
     */
    const msalConfig = {
      auth: {
        clientId:
          process.env.CLIENT_ID ||
          "Enter_the_Edit_Profile_Service_Application_Id_Here", // 'Application (client) ID' of the Edit_Profile Service App registration in Microsoft Entra admin center - this value is a GUID
        authority:
          process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, // Replace the placeholder with your external tenant name
      },
      system: {
        loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
            console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: "Info",
        },
      },
    };
    
    const GRAPH_API_ENDPOINT = process.env.GRAPH_API_ENDPOINT || "graph_end_point";
    // Refers to the user that is single user singed in.
    // https://learn.microsoft.com/en-us/graph/api/user-update?tabs=http
    const GRAPH_ME_ENDPOINT = GRAPH_API_ENDPOINT + "v1.0/me";
    
    module.exports = {
      msalConfig,
      REDIRECT_URI,
      POST_LOGOUT_REDIRECT_URI,
      TENANT_SUBDOMAIN,
      GRAPH_API_ENDPOINT,
      GRAPH_ME_ENDPOINT,
      TENANT_ID,
    };
    

    Temukan tempat penampung:

    • Enter_the_Tenant_Subdomain_Here dan ganti dengan subdomain Direktori (penyewa). Misalnya, jika domain utama penyewa Anda adalah contoso.onmicrosoft.com, gunakan contoso. Jika Anda tidak memiliki nama penyewa, pelajari cara membaca detail penyewa Anda.
    • Enter_the_Tenant_ID_Here dan ganti dengan ID Penyewa. Jika Anda tidak memiliki ID Penyewa, pelajari cara membaca detail penyewa Anda.
    • Enter_the_Edit_Profile_Service_Application_Id_Here dan menggantinya dengan adalah nilai ID Aplikasi (klien) dari EditProfileService yang Anda daftarkan sebelumnya.
    • Enter_the_Client_Secret_Here dan ganti dengan nilai rahasia aplikasi EditProfileService yang Anda salin sebelumnya.
    • graph_end_point dan ganti dengan titik akhir Microsoft Graph API, yaitu https://graph.microsoft.com/.
  2. Di editor kode Anda, buka file Api/fetch.js , lalu tempelkan kode dari file Api/fetch.js . Fungsi ini fetch menggunakan token akses dan titik akhir sumber daya untuk melakukan panggilan API aktual.

  3. Di editor kode Anda, buka file Api/index.js , lalu tempelkan kode dari file Api/index.js .

Memperoleh token akses dengan menggunakan acquireTokenOnBehalfOf

Dalam file Api/index.js, aplikasi tingkat menengah (aplikasi EditProfileService) memperoleh token akses menggunakan fungsi acquireTokenOnBehalfOf, yang digunakannya untuk memperbarui profil atas nama pengguna tersebut.

async function getAccessToken(tokenRequest) {
  try {
    const response = await cca.acquireTokenOnBehalfOf(tokenRequest);
    return response.accessToken;
  } catch (error) {
    console.error("Error acquiring token:", error);
    throw error;
  }
}

Parameter tokenRequest didefinisikan seperti yang ditunjukkan kode berikut:

    const tokenRequest = {
      oboAssertion: req.headers.authorization.replace("Bearer ", ""),
      authority: `https://${TENANT_SUBDOMAIN}.ciamlogin.com/${TENANT_ID}`,
      scopes: ["User.ReadWrite"],
      correlationId: `${uuidv4()}`,
    };

Dalam file yang sama, API/index.js, aplikasi tingkat menengah (aplikasi EditProfileService) melakukan panggilan ke Microsoft Graph API untuk memperbarui profil pengguna:

   let accessToken = await getAccessToken(tokenRequest);
    fetch(GRAPH_ME_ENDPOINT, accessToken, "PATCH", req.body)
      .then((response) => {
        if (response.status === 204) {
          res.status(response.status);
          res.json({ message: "Success" });
        } else {
          res.status(502);
          res.json({ message: "Failed, " + response.body });
        }
      })
      .catch((error) => {
        res.status(502);
        res.json({ message: "Failed, " + error });
      });

Menguji aplikasi Anda

Untuk menguji aplikasi Anda, gunakan langkah-langkah berikut:

  1. Untuk menjalankan aplikasi klien, bentuk jendela terminal, navigasikan ke direktori Aplikasi , lalu jalankan perintah berikut:

    npm start
    
  2. Untuk menjalankan aplikasi klien, bentuk jendela terminal, navigasikan ke direktori API , lalu jalankan perintah berikut:

    npm start
    
  3. Buka browser Anda, lalu buka http://localhost:3000. Jika Anda mengalami kesalahan sertifikat SSL, buat .env file, lalu tambahkan konfigurasi berikut:

    # Use this variable only in the development environment. 
    # Remove the variable when you move the app to the production environment.
    NODE_TLS_REJECT_UNAUTHORIZED='0'
    
  4. Pilih tombol Masuk , lalu Anda masuk.

  5. Pada halaman masuk, ketik alamat Email Anda, pilih Berikutnya, ketik Kata Sandi Anda, lalu pilih Masuk. Jika Anda tidak memiliki akun, pilih Tidak ada akun? Buat satu tautan, yang memulai alur pendaftaran.

  6. Untuk memperbarui profil, pilih tautan Pengeditan profil. Anda melihat halaman yang mirip dengan cuplikan layar berikut:

    Cuplikan layar profil pembaruan pengguna.

  7. Untuk mengedit profil, pilih tombol Edit Profil . Jika Anda belum melakukannya, aplikasi akan meminta Anda untuk menyelesaikan tantangan MFA.

  8. Buat perubahan pada salah satu detail profil, lalu pilih tombol Simpan .