یک وبسایت ساده Node.js به همراه احراز هویت کاربر - بخش دوم

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 12 مهر 1397
دسته بندی ها : نود جی اس

در بخش قبل، تمام viewهای مورد نیاز را ساختیم. حال نوبت به رسیدگی به routeها می‌رسد.

جدول محتویات:

  1. Routeهای عمومی را بسازید
  2. Routeها را فعال‌سازی کنید
  3. احراز هویت کاربر را پیکربندی کنید
  4. نحوه کار احراز هویت
  5. استایل‌بندی‌ها را تنظیم کنید
  6. درگاه ورود جدید خود را آزمایش کنید

Routeهای عمومی را بسازید

Routeها در Express.js مکانی هستند که منطق برنامه را تعریف می‌کنید. آن‌ها تعیین می‌کنند که وقتی که یک URL کاربر توسط کاربر کلیک شده است،‌ چه کدی اجرا شود و چه پاسخی باز گردانده شود.

برای شروع، بیایید routeهای پیشفرض که express-generator برای شما ساخته است را حذف کنیم. دستور زیر را برای حدف آن‌ها اجرا کنید:

rm routes/*

سپس، فایلی به نام ./routes/public.js بسازید و این کد را در آن قرار دهید:

const express = require("express");

const router = express.Router();

// Home page

router.get("/", (req, res) => {

  res.render("index");

});

module.exports = router;

در این ماژول، یک Express.js Router جدید می‌سازیم و به آن می‌گوییم که اگر یک کاربر یک درخواست GET را به URL مورد نظر ارسال کرد، تابعی را اجرا کند که فایل indes.pug را که پیش‌تر ساختیم را رندر کند و به کاربر برگرداند.

حال این کار فعلا عمل نخواهد کرد، (به دلایلی که بعدا پی می‌برید) اما وقتی که این router فعال شده است، هر زمان که کاربری برای صفحه اصلی وبسایت درخواست می‌کند، (مثلا http://localhost:3000) این کد فایل index.pug را اجرا کرده و نمایش می‌دهد.

سپس، فایلی به نام ./routes/dashboard.js بسازید و این کد را در آن قرار دهید:

const express = require("express");

const router = express.Router();

// نمایش صفحه داشبورد

router.get("/", (req, res) => {

  res.render("dashboard");

});

module.exports = router;

این route مشابه router صفحه اصلی در بالا عمل می‌کند، اما فقط داشبورد ما را رندر می‌کند. در حالیکه فعلا غیر منطقی به نظر می‌رسد، اگر در نهایت کاربری از صفحه /dashboard بازدید کند، این تابع اجرا می‌شود و فایل dashboard.pug که پیش‌تر تعریف کردیم را رندر می‌کند.

اگر می‌خواستید که به این فایل بروید و یک route دیگر تعریف کنید، مثلا:

router.get("/test", (req, res) => {

  res.render("test");

});

می‌بینید که یک کاربر از /dashboard/test بازدید می‌کند تا تابع را فعال کرده، و اجرا می‌کند. دوباره تاکید می‌کنم که نگران غیر منطقی بودن این مسئله نباشید.

Routeها را فعال‌سازی کنید

حال که چند route برای صفحات عمومی ساخته‌اید، بیایید آن‌ها را با Express.js فعال کنیم و بر روی کار ببینیم.

برای انجام این کار، فایل ./app.js را باز کرده، و این دو خط را حذف کنید:

var indexRouter = require('./routes/index');

var usersRouter = require('./routes/users');

آن دو خط را با دو خط زیر جایگزین کنید:

const dashboardRouter = require("./routes/dashboard");         

const publicRouter = require("./routes/public");

حال، فایل‌های route صحیح که در بالا تعریف کردیم را پیاده‌سازی می‌کنیم.

سپس، صفحه را به پایین اسکرول کنید، تا به این دو خط برسید و آن‌ها را حذف کنید:

app.use('/', indexRouter);

app.use('/users', usersRouter);

آن دو خط، routeهای قدیمی که حذف کردیم را بارگذاری می‌کردند. حال باید آن دو خط را به این صورت تغییر دهید:

app.use('/', publicRouter);

app.use('/dashboard', dashboardRouter);

حال این کد، کم کم منطقی به نظر می‌رسد. این خط‌های app.use‌ موجود در کد، به Express.js می‌گویند که اگر کاربری از URL مورد نظر بازدید می‌کند، باید به فایل ./routes/public.js مراجعه کند و URLهای موجود در آن را تطبیق دهد. پس اگر کاربری از صفحه اصلی بازدید می‌کند، Express.js به فایل ./routes/public.js مراجعه می‌کند، route مربوط به URL مورد نظر را پیدا می‌کند و سپس توابع مربوطه را اجرا می‌کند.

همین اتفاق، برای dashboardRouter موجود در زیر نیز می‌افتد. اگر کاربری از /dashboard بازدید کند، Express.js به فایل ./routes/dashboard.js مراجعه می‌کند و تابعی را اجرا می‌کند که وقتی URL مورد نظر فراخوانی می‌شود، باید اجرا شود؛ زیرا /dashboard + URL مسیری است که کاربر در حال بازدید از آن است.

Routeها در Express.js، ساختن وبسایت‌های پیچیده با مقدار زیادی URLهای در هم و میزان زیادی از کار را ساده‌تر می‌کنند.

حال که routeهای خود را فعال کرده‌اید، آن‌ها را آزمایش کنید. وب سرور خود را با اجرای دستور زیر شروع کنید:

npm start

سپس در مرورگر خود به آدرس http://localhost:3000 بروید. باید این صفحه را مشاهده کنید:

نکته: این صفحه هنوز درست به نظر نمی‌رسد، زیرا هنوز هیچ CSSای به آن اضافه نکرده‌ایم. این کار را در آخر انجام خواهیم داد.

اگر حالا بروید و داشبوردی که ساختیم، یا به عبارتی http://localhost:3000/dashboard را نگاه کنید، متوجه یک خطا می‌شوید. علت آن این است که view پاگ، به متغیر #{user} اشاره دارد که هنوز تعریف نکرده‌ایم. به زودی به آن بخش نیز خواهیم رسید.

احراز هویت کاربر را پیکربندی کنید

حال که وبسایت Express.js ما در حال شروع و عملکردی شدن است، بیایید بیشتر به احراز هویت کاربر وارد شویم.

اولین کاری که باید انجام دهید، باز کردن فایل ./app.js و وارد کردن دو کتابخانه زیر، در بالای فایل است.

var createError = require('http-errors');

var express = require('express');

var path = require('path');

var cookieParser = require('cookie-parser');

var logger = require('morgan');

var session = require('express-session');

var okta = require("@okta/okta-sdk-nodejs");

var ExpressOIDC = require("@okta/oidc-middleware").ExpressOIDC;

دو کتابخانه‌ای که اضافه کردیم، در پایین لیست قرار دارند: @okta/okta-sdk-nodejs و @okta/oidc-middleware. این دو کتابخانه تمام ارتباطات و routingهای OpenID Connect را مدیریت می‌کنند.

کار بعدی که باید انجام دهیم، ساخت آبجکتی به نام oktaClient و همچنین آبجکت ExpressOIDC است. این دو مورد، پس از این که آن‌ها را پیکربندی کرده و مجوزهای صحیح را به آن‌ها دادیم، مورد استفاده قرار خواهند گرفت.

برای انجام این کار، فایل ./app.js خود را باز کنید، خطی که شامل var app = express(); است را پیدا کنید و این کد را سریعا در زیر آن وارد کنید:

var oktaClient = new okta.Client({

  orgUrl: '{yourOktaOrgUrl}',

  token: '{yourOktaToken}'

});

const oidc = new ExpressOIDC({

  issuer: "{yourOktaOrgUrl}/oauth2/default",

  client_id: {yourClientId},

  client_secret: {yourClientSecret},

  redirect_uri: 'http://localhost:3000/users/callback',

  scope: "openid profile",

  routes: {

    login: {

      path: "/users/login"

    },

    callback: {

      path: "/users/callback",

      defaultRedirect: "/dashboard"

    }

  }

});

حال مقادیری که در ابتدای مقاله گفتم کپی کنید را به یاد دارید؟ در اینجا به آن‌ها نیاز دارید. مطمئن شوید که این مقادیر را با مقادیر صحیح جایگزین می‌کنید: {yourOkraOrgUrl}، {yourOktaToken}، {yourClientID} و {yourClientSecret}.

آبجکت oidc که ساختیم، تمام جنبه‌های پشتیبانی پروتکل OpenID Connect را مدیریت می‌کند، router را مدیریت می‌کند تا به ثبت نام، ورود و بازنشانی پسوورد کاربر رسیدگی کند، و همچنین وارد شدن به برنامه شما به صورت امن با استفاده از کوکی‌ها را نیز مدیریت می‌کند.

آبجکت oktaClient صرفا برای گرفتن داده‌های کاربر از سرویس Okta APU استفاده می‌شود.

حال که پشتیبانی OpenID Connect ما آماده استفاده است، بیایید آن را فعال کنیم. برای انجام این کار، فایل ./app.js را باز کنید، session middleware که پیش‌تر به آن اشاره شد را پیدا کنید، و سپس این کد را در زیر آن اضافه کنید:

app.use(session({

  secret: 'asdf;lkjh3lkjh235l23h5l235kjh',

  resave: true,

  saveUninitialized: false

}));

app.use(oidc.router);

فراخوانی app.use(oidc.router); تمام چیزی است که نیاز داریم، تا به Express.js بگوییم که routeهای شامل کتابخانه oidc-middleware را برای مدیریت پشتیبانی OpenID Connect فعال کند. ممکن است که پیش‌تر و در هنگام ساخت آبجکت oidc متوجه شده باشید که چند route را در هنگام پیکربندی تعیین کردیم. این تنظیمات تعیین می‌کنند که ما از چه URLهایی می‌خواهیم برای مدیریت ورود کاربران استفاده کنیم، و پس از این که کاربران وارد شدند، آن‌ها را به چه URLای می‌خواهیم منتقل کنیم.

یکی از منافع فعال بودن این router، این است که از اینجا به بعد، در هر کدام از کدهای route شما، به یک متغیر خاص، یعنی req.userinfo دسترسی خواهیم داشت، که شامل برخی اطلاعات پروفایل کاربر فعلی می‌شود.

و درحالیکه req.userinfo اینقدر خوب است،  خیلی بهتر می‌شد اگر می‌توانستیم هر داده‌ای که می‌خواهیم را درباره کاربر فعلی به دست بیاوریم.

بیایید یک middleware دیگر تعریف کنیم، تا در این مسئله به ما کمک کند. در قسمت پایین کد app.use(oidc.router);، این کد را وارد کنید:

app.use((req, res, next) => {

  if (!req.userinfo) {

    return next();

  }

  oktaClient.getUser(req.userinfo.sub)

    .then(user => {

      req.user = user;

      res.locals.user = user;

      next();

    }).catch(err => {

      next(err);

    });

});

این middleware بر روی هر درخواست کاربر اجرا می‌شود،‌ و این کارها را انجام می‌دهد:

  • با نگاه به آبجکت req.userinfo، بررسی می‌کند که در حال حاضر کاربری وارد شده است، یا خیر. اگر هیچ کاربری وجود ندارد، هیچ کاری انجام نمی‌دهد. (return next();)
  • اگر کاربری وجود داشته باشد، این middleware از کتابخانه Okta Node SDK استفاده می‌کند،‌ تا آبجکت کاربر را از Okta API دریافت کند.
  • در آخر، دو مقدار جدید می‌سازد: req.user و req.locals.user که مستقیما به آبجکت کاربر مربوط می‌شوند.

این به این معنی است که در هر routeای که بعدا تعریف کنیم، می‌توانیم مستقیما به آبجکت req.user دسترسی داشته باشیم تا اطلاعات کاربر را ببینیم، ویرایش کنیم، یا حتی حذف کنیم.

برای مثال،  می‌توانید route زیر را تعریف کنید،‌ تا هر زمان که کاربری از URLای مانند /test بازدید می‌کند، اطلاعات او را نمایش دهید:

app.get('/test', (req, res) => {

  res.json({ profile: req.user ? req.user.profile : null });

});

حال بیایید یک middleware دیگر نیز، به نام loginRequired بسازیم، که اگر کاربری از قبل وارد شده است، به او اجازه می‌دهد که از یک route بازدید کند. این زمانی به کار می‌آید که شما می‌خواهید صفحه‌ای بسازید که فقط کاربران وارد شده می‌توانند ببینند. (مانند داشبورد و...)

در قسمت پایین کد بالا، تابع زیر را تعریف کنید:

function loginRequired(req, res, next) {

  if (!req.user) {

    return res.status(401).render("unauthenticated");

  }

  next();

}

از آنجایی که می‌خواهیم مطمئن شویم فقط کاربران وارد شده می‌توانند صفحه داشبورد را ببینند، بیایید برگردیم و کد مربوط به داشبورد را تغییر دهیم.

در فایل ./app.js، آن خط از کد که route داشبورد را فعال کرد را پیدا کنید:

app.use('/dashboard', dashboardRouter);      

حال، آن را به این صورت ویرایش کنید:

app.use('/dashboard', loginRequired, dashboardRouter);

با وارد کردن تابع loginRequired پس از الگوی URL، در ابتدا Express.js قبل از این که dashboardRouter پردازش شود، loginRequired را اجرا می‌کند. به این صورت، اگر کاربری از هر صفحه‌ای که با /dashboard شروع شود بازدید کند، برایشان واجب می‌شود که قبل از دسترسی به آن، وارد شوند.

آخرین کاری که باید برای تکمیل کامپوننت احراز هویتمان انجام دهیم، تعریف یک route برای خروج است. کتابخانه oidc-middleware عملکرد خروج را فراهم می‌کند، اما به طور خودکار هیج routeای برایش نمی‌سازد.

برای انجام این کار، فایلی به نام ./routes/users.js بسازید و این کد را به جای کد موجود در آن قرار دهید:

const express = require("express");

const router = express.Router();

// خروج یک کاربر

router.get("/logout", (req, res) => {

  req.logout();

  res.redirect("/");

});

module.exports = router;

همانطور که می‌توانید حدث بزنید، این route، اگر کاربری یک درخواست POST به /users/logout ارسال کند، آن‌ها را خارج می‌کند. تنها کاری که باید انجام دهیم، فعال کردن این routeدر فایل ./app.js است.

فایل ./app.js را باز کرده، و فایل route جدید را به همراه فایل‌های دیگر وارد کنید:

const dashboardRouter = require("./routes/dashboard");

const publicRouter = require("./routes/public");

const usersRouter = require("./routes/users");

سپس، صفحه را به پایین اسکرول کنید تا ببینید که دیگر routerها در حال فعال شدن هستند، و این router را نیز فعال کنید:

app.use('/', publicRouter);

app.use('/dashboard', loginRequired, dashboardRouter);

app.use('/users', usersRouter);

تبریک، حال یک سیستم مدیریت و احراز هویت کاربر را به کلی برای وبسایت خود پیکربندی کرده‌ای،. و هیچ نیازی به نوشتن کد، مدیریت رمز عبور، ذخیره سازی چیزی در دیتابیس و... نبود.

نحوه کار احراز هویت

حال که دیدید چگونه احراز هویت را برای وبسایت Node.js خود راه‌اندازی کنید، بیایید کمی بیشتر درباره نحوه کار آن بدانیم و کل جریان کاری آن را بررسی کنیم.

برای توضیح هر کامپوننت، بیایید فرض کنیم که شما در حال بازدید از یک وبسایت هستید و در حال حاضر به حساب خود وارد نشده‌اید.

وقتی که بر روی دکمه Log In / Register در بالای صفحه کلیک می‌کنید، کتابخانه oidc-middleware شما را به دامنه Okta (سرور احراز هویت) منتقل می‌کند. در زیر نوع URL که به آن منتقل می‌شوید را می‌بینید:

https://dev-842917.oktapreview.com/login/login.htm?fromURI=/oauth2/v1/authorize/redirect?okta_key=qBpZVCpQIJlxUALtybnI9oajmFSOmWJNKL9pDpGtZRU

نکته: می‌توانید نام و ظاهر این دامنه را با استفاده از Okta سفارشی‌سازی کنید.

پس از این که به صفحه سرور احراز هویت وارد شدید، می‌توانید اطلاعات حساب خود را وارد کرده و وارد شوید، یا یک حساب جدید بسازید. این عملکرد، به کلی توسط سرور احراز هویت مدیریت می‌شود.

اگر اطلاعات مورد نیاز را وارد کرده و بر روی دکمه Sign In در سرور احراز هویت کلیک کنید، در پشت پرده این اتفاقات می‌افتند:

  • رمز عبور شما hash شده، و اطلاعات ورود شما در مقابل دیتابیس کاربران Okta بررسی می‌شوند، تا بررسی شود که آیا صحیح هستند، یا نه.
  • اگر اطلاعات شما صحیح هستند، یک کوکی session جدید برای شما بر روی دامنه Okta ساخته می‌شود و به تنظیمات redirect_uri که قبلا در هنگام تعریف آبجکت ExpressOIDC انتخاب کردید، منتقل می‌شوید. پس از این که به این URL منتقل شدید، سرور احراز هویت یک کد نشانه ویژه را نیز منتقل می‌کند.
  • برنامه Express.js شما، درخواست را در /users/callback دریافت می‌کند و آن را به طور خودکار و با استفاده از routeهای داخلی کتابخانه oidc-middleware بررسی می‌کند. این route درخواست مورد نظر را رهگیری می‌کند و کد نشانه با را نشانه‌های access و id جابه‌جا می‌کند. روند جابه‌جایی کد نشانه بخشی از جریان کاری احراز هویت OpenID Connect است.
  • پس از این که این نشانه دریافت شد، کتابخانه oidc-middleware اطلاعات پایه کاربر را در نشانه id جایگذاری می‌کند و در کوکی session ذخیره می‌کند.
  • سپس کتابخانه oidc-middleware شما را به عنوان یک کاربر وارد شده، به صفحه داشبورد منتقل می‌کند.
  • از اینجا به بعد، هر بار که مرورگر شما درخواستی به Express.js ارسال می‌کند، کوکی‌ای که شامل اطلاعات پروفایل شما می‌شود، به Express.js برگردانده می‌شود، تا کتابخانه oidc-middleware بتواند تشخیص دهد که شما چه کسی هستید و آبجکت req.userinfo را با داده‌های حساب شما پر کند.

پس از این که کوکی session شما منقضی شد، (یا توسط یک logout از بین رفت) کل روند از اول شروع می‌شود.

استایل‌بندی‌ها را تنظیم کنید

من یک طراح حرفه‌ای نیستم، اما می‌توانم ظاهر این وبسایت را کمی بهتر کنم.

فایلی به نام ./public/stylesheet/style.css بسازید و این کد CSS را در آن قرار دهید:

.top-bar a {

  text-decoration: none;

  color: inherit;

}

footer {

  border-top: 1px solid rgba(0,0,0,.1);

  margin-top: 4em !important;

  padding-top: 1em;

  text-align: center;

  margin-top: 1em;

}

h2 {

  margin-bottom: 2em;

}

.container {

  padding-top: 2em;

}

این کد، ظاهر وبسایت را کمی بهتر می‌کند.

درگاه ورود جدید خود را آزمایش کنید

حال که وبسایت Express.js شما ساخته شده است، چرا آن را آزمایش نکنیم؟ با اجرای دستور nps start، وب سرور خود را شروع کنید، صفحه http://localhost:3000 را باز کنید و وبسایت خود را آزمایش کنید.

در اینجا، متوجه چند نکته خواهید شد:

  • اگر بر روی دکمه Log In / Register در بالای صفحه کلیک کنید، می‌توانید یک حساب کاربری جدید بسازید، یا به یک حساب موجود وارد شوید. این عملکرد، تماما به طور خودکار توسط سرور احراز هویت Okta انجام می‌شود.
  • پس از این که وارد شدید، به صفحه /dashboard منتقل می‌شوید، و با نمایش اسم کوچک شما، به شما خوش‌آمد گفته می‌شود. متغیر #{user.profile.firstname} در فایل ./views/dashboard.pug را به یاد دارید؟ حال آن متغیر، حساب کاربری شما است.
  • اگر از حساب خود خارج شدید، سریعا بر روی دکمه Log In / Register کلیک کنید و می‌بینید بدون نیاز به نام کاربری و رمز عبور خود، مجددا وارد حساب خود می‌شوید. این یکی از ویژگی‌های OpenID Connect است. سرور احراز هویت، برای مدتی به یاد می‌سپارد که شما چه کسی هستید. Google Login و Facebook Login نیز به همین صورت کار می‌کنند.

امیدوارم که از این مقاله لذت برده باشید. راه‌اندازی سیستم احراز هویت می‌تواند یک عذاب بزرگ باشد، اما با کمک OpenID Connect این فرایند بسیار آسان شده است.

منبع

مقالات پیشنهادی

یک وبسایت ساده Node.js به همراه احراز هویت کاربر - بخش اول

ساخت یک وبسایت با احراز هویت کاربر و مدیریت (ورود، ثبت نام، بازنشانی رمز عبور و...) می‌توانند یک عذاب بزرگ باشند. به عنوان یک توسعه‌دهنده، میلیون‌ها م...

یک تجربه کاربری کامل : واقعیت یا خیال ؟

روی تجربه کاربری(ux) تمرکز کنید، شگفت آوره، هنوز هم می تونيد روی یک پروژه که به نظريات اون تسلط داريد چيره بشيد. در حالي كه بر اساس قوانين تصميم گيري...

5 سوال ساده برای وقتی که به دنبال بازخورد کاربر هستید

شما نمی توانید اولین برداشت را از نو بسازید درسته؟ البته که درسته و این یکی از دلایلی است که باید وقت و تلاش زیادی صرف طراحی یک وب سایت کنید . شما می...

بهبود امنیت با بهتر‌ کردن تجربه‌کاربری

کاربرانی که در اینترنت جستجو می‌کنند و نظاره‌گر هستند، همیشه دوست دارند که وبسایت‌های مقصود‌شان امن و کاربرپسند باشند. آن‌ها تنها به شرکت‌هایی اعتماد...