اولین برنامه Deno خود را با قابلیت احراز هویت بسازید

ترجمه و تالیف : عرفان حشمتی
تاریخ انتشار : 24 دی 99
خواندن در 5 دقیقه
دسته بندی ها : نود جی اس

رایان دال - خالق Node.js – فریمورک جدیدی را برای طراحی برنامه‌های وب طراحی کرده است. او با استفاده از فناوری‌های جدیدی که در زمان نوشتن Node در دسترس نبود، اشتباهاتی که صورت گرفته بود را برطرف کرد و نتیجه آن Deno است. فریمورکی برای نوشتن برنامه‌های وب. در این مقاله شما را در ایجاد یک برنامه وب همراه با احراز هویت راهنمایی می‌کنیم.

تقریبا تمام اطلاعات مورد نیاز خود را می‌توانید در وب سایت Deno - همراه با اطلاعات مربوط به کتابخانه‌های شخص ثالث موجود برای آن - پیدا کنید. این در حال حاضر بزرگترین نقص در فریمورک است. Deno در تاریخ 13 مه سال 2020 به نسخه 1.0 رسید، بنابراین حتی اگر تعداد زیادی کتابخانه ضروری وجود داشته باشد، باز هم به تعداد کتابخانه‌های موجود در Node نمی‌رسد. برای کسانی که در Node مهارت کافی دارند، کار با Deno باید بسیار راحت باشد.

همچنین می‌توانید دستورالعمل نصب را در آدرس https://deno.land/#installation پیدا کنید.

برنامه Deno خود را ایجاد کنید

هیچ کتابخانه قالب بندی نتوانستم پیدا کنم، بنابراین فقط با یک پوشه خالی شروع می‌کنیم. در پوشه روت برنامه فایلی به نام index.ts ایجاد کنید که نقطه شروع برنامه Deno شما باشد. همچنین از Opine استفاده خواهید کرد که یک Clone Express برای Deno است تا ساخت و مسیریابی را آسان‌تر کند.

نکته‌ای که در مورد Deno متفاوت است، این است که هیچ مدیریت پکیجی برای آوردن کتابخانه‌های شخص ثالث وجود ندارد. این کار را با استفاده از آدرس اینترنتی کامل کتابخانه انجام می‌دهیم و آن را در بالای فایل index.ts اضافه می‌کنیم، سپس وب اپلیکیشن اصلی را تنظیم می‌کنیم.

import { opine } from 'https://deno.land/x/opine@0.12.0/mod.ts';

const app = opine();

app.get('/', (req, res) => {
  res.send('Deno Sample');
});

app.listen(3000);
console.log('running on port 3000');

بعد با رفتن به ترمینال در پوشه برنامه و وارد کردن، می‌توانید این برنامه ابتدایی را اجرا کنید.

deno run -A index.ts

A- میانبری برای اهداف توسعه است. Deno به طور پیش فرض کاملا قفل شده است، بنابراین باید آرگومان‌هایی را به دستور run منتقل کنید تا اجازه دسترسی مانند allow-net-- برای شبکه و allow-read-- برای خواندن برنامه از سیستم فایل را فراهم کنید. A- استفاده شده در اینجا به طور موثر تمام امنیت را از کار می‌اندازد. هنگامی که این برنامه را اجرا می‌کنید و سپس به http://localhost:3000 می‌روید، باید با Deno Sample در یک صفحه خالی از شما استقبال شود.

با Deno یک وب اپلیکیشن واقعی بسازید

اگرچه این اولین گام برای شروع کار است، اما خیلی کاربردی نیست. شما می‌توانید برخی از قابلیت‌هایی را اضافه کنید که کمی واقعی‌تر هستند، بنابراین فایل index.ts را تغییر دهید تا به صورت زیر باشد:

import { opine, serveStatic } from 'https://deno.land/x/opine@0.12.0/mod.ts';
import { renderFileToString } from 'https://deno.land/x/dejs@0.7.0/mod.ts';
import { join, dirname } from 'https://deno.land/x/opine@main/deps.ts';

import { ensureAuthenticated } from './middleware/authmiddleware.ts';
import users from './controllers/usercontroller.ts';
import auth from './controllers/authcontroller.ts';

const app = opine();
const __dirname = dirname(import.meta.url);

app.engine('.html', renderFileToString);
app.use(serveStatic(join(__dirname, 'public')));
app.set('view engine', 'html');

app.get('/', (req, res) => {
  res.render('index', { title: 'Deno Sample' });
});

app.use('/users', ensureAuthenticated, users);
app.use('/auth', auth)

app.listen(3000);
console.log('running on port 3000');

برخی از اعلانات دیگر که کتابخانه‌های شخص ثالث را import می‌کنند، مشاهده خواهید کرد. در اینجا من از dejs استفاده می‌کنم که یک درگاه EJS برای Deno است. همچنین برخی کلاس‌های ابزار را از کتابخانه Opine برای دستکاری در نام فهرست‌ها گنجانده‌ام. در ادامه توضیح خواهم داد که سه فایل import شده به صورت محلی چیست. در حال حاضر فقط بدانید که آنها را باید وارد کنید.

خط زیر ()opine مرجعی را به دایرکتوری محلی وارد می‌کند. سه خط بعدی برای تنظیم موتور مشاهده روی DEJS به منظور پردازش فایل‌های مشابه HTML استفاده می‌شود، مشابه روشی که EJS برای Node انجام می‌دهد. بخش بعدی کمی تغییر کرده است تا یکی از فایلهای الگوی HTML ارائه شود و دو خط آخر برخی مسیرهای خارجی را به همراه دارد. نکته قابل توجه این است که مسیر users/ دارای تابع میان‌افزار ()ensureAuthenticated است. این کار کاربران را مجبور می‌کند قبل از اجازه بازدید از صفحه، وارد سیستم شوند. در ادامه این میان‌افزار را ایجاد خواهیم کرد.

برنامه Deno خود را کامل کنید

اکنون می‌خواهیم برخی از قطعات جا مانده در بالا را وارد کنیم. با مسیرها شروع می‌کنیم. پوشه‌ای به نام controllers در روت برنامه ایجاد کنید. سپس یک فایل usercontroller.ts در داخل آن با محتوای زیر اضافه کنید:

import { Router } from 'https://deno.land/x/opine@0.12.0/mod.ts';

const users = new Router();

// users routes
users.get('/me', (req, res) => {
  res.render('users/me', { title: 'My Profile', user: res.app.locals.user });
});

export default users;

این یک فایل مسیریابی ساده است. روتر را از Opine می‌گیرد و نمونه جدیدی را برای اتصال مسیرها ایجاد می‌کند. سپس کدی برای افزودن مسیر در /me وجود دارد تا بتواند نمای HTML را در users/me نمایش دهد. فراخوانی ()render عنوان و کاربر وارد شده را به صفحه منتقل می‌کند. این صفحه محافظت می‌شود تا همیشه کاربری برای ورود به صفحه وجود داشته باشد.

در مرحله بعدی، نماهایی را ایجاد می‌کنیم تا هنگام ضربه زدن به مسیرها نشان داده شود. در پوشه روت، یک پوشه views اضافه کنید. در داخل آن یک پوشه shared و یک پوشه users ایجاد کنید. در پوشه shared یک فایل header.html و footer.html ایجاد کنید. در پوشه users هم یک فایل me.html بسازید. سرانجام در پوشه views خود یک فایل index.html اضافه کنید.

اینها ستون‌های برنامه هستند، اما نحوه ایجاد نماهایی را نشان می‌دهند که توسط سایر نماها قابل استفاده مجدد است. در فایل shared/header.html موارد زیر را اضافه کنید:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title><%= title %></title>
</head>

<body>

با این کار از بالای صفحه HTML خارج می‌شوید و عنوان را به صفحه تزریق می‌کنید. سپس موارد زیر را به فایل footer.html/shared اضافه کنید:

</body>

</html>

اکنون می‌توانید از این جزئیات در فایل index.html استفاده کنید:

<%- await include('views/shared/header.html', { title }); %>

<a href="/users/me">My Profile</a>

<%- await include('views/shared/footer.html'); %>

این شامل قسمت‌های footer و header است و لینکی به صفحه پروفایل اضافه می‌کند. محتویات فایل users/me.html عبارتند از:

<%- await include('views/shared/header.html', { title }); %>

<h1>My Profile</h1>

<ul>
<% for(var p in user){ %>
  <li><strong><%= p %>: </strong><%= user[p] %></li>
<% } %>
</ul>

<%- await include('views/shared/footer.html'); %>
 

باز هم این صفحه شامل header و footer است. مسلما این یک صفحه view فوق‌العاده جذاب نیست، اما به شما اطلاع می‌دهد که مراحل احراز هویت به درستی انجام شده است.

احراز هویت را با Okta انجام دهید

اگر حساب Okta ندارید، می‌توانید یک حساب توسعه دهنده رایگان در اینجا دریافت کنید. پس از ورود به سیستم Okta، روی داشبورد قرار خواهید گرفت. برای استفاده از Okta به عنوان یک احراز کننده هویت برای پروژه خود، باید یک برنامه Okta ایجاد کنید.

روی Applications در فهرست کلیک کنید و سپس Add Application را بزنید. این شما را به wizard برنامه می‌برد. وب را برای پلتفرم خود انتخاب کنید، سپس روی Next کلیک کنید. صفحه بعدی صفحه Application Settings است. برای برنامه خود یک اسم انتخاب کنید (من اسم را DenoExample گذاشتم). همه URL ها را تغییر دهید تا از پورت 3000 به جای 8080 استفاده کنید. سپس URL های تغییر مسیر ورود به سیستم را به http://localhost:3000/auth/callback تغییر دهید. این مسیری است که به زودی اجرا می‌کنید. در آخر هم برای انجام ایجاد برنامه در Okta، روی Done کلیک کنید.

هنگامی که به صفحه برنامه تازه ایجاد شده خود رسیدید، مطمئن شوید که در برگه General Settings هستید و به پایین پیمایش کنید تا قسمت Client Credentials را ببینید. شما به صورت لحظه‌ای از این مقادیر استفاده خواهید کرد، بنابراین این پنجره را باز نگه دارید.

به برنامه خود برگردید، یک فایل جدید در روت برنامه به نام env. ایجاد کنید. محتویات فایل به شرح زیر خواهد بود:

issuer=https://{yourOktaOrgUrl}/oauth2/default
clientId={yourClientID}
clientSecret={yourClientSecret}
redirectUrl=http://localhost:3000/auth/callback
state=SuPeR-lOnG-sEcReT

Client ID و Client Secret را از بخش Client Credentials برنامه Okta خود کپی کنید. سپس به داشبورد برگردید و URL خود را از سمت راست و درست در زیر منو کپی کنید.

اکنون آماده هستید که برای تأیید اعتبار با Okta ارتباط برقرار کنید. متأسفانه من هیچ کتابخانه OpenID Connect (OIDC) را پیدا نکردم تا احراز هویت با OAuth 2.0 و OIDC را آسان‌تر از این انجام دهد، بنابراین شما باید آن را به صورت دستی ایجاد کنید. با این حال، این کار می‌تواند یک تمرین عالی باشد تا به درک نحوه کار OAuth و OIDC کمک کند. در پوشه اصلی برنامه خود، پوشه جدیدی به نام middleware ایجاد کنید و فایلی به نام authmiddleware.ts اضافه کنید. سپس این محتوا را در فایل قرار دهید:

import { config } from 'https://deno.land/x/dotenv/mod.ts';

export const ensureAuthenticated = async (req:any, res:any, next:any) => {
  const user = req.app.locals.user;
  if(!user){
    const reqUrl = req.originalUrl;
    const {issuer, clientId, redirectUrl, state} = config();
    const authUrl = `${issuer}/v1/authorize?client_id=${clientId}&response_type=code&scope=openid%20email%20profile&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}:${reqUrl}`;
    res.location(authUrl).sendStatus(302);
  }
  next();
}

ابتدا یک کتابخانه برای خواندن فایل env. بیاورید. dotenv این کار را به خوبی انجام می‌دهد. سپس ()ensureAuthenticated را پیاده سازی کنید تا اولین مرحله از احراز هویت شروع شود. بعد بررسی می‌کند که کاربر از قبل وارد سیستم نشده است. درصورت ورود به سیستم، فقط ()next را فراخوانی می‌کند، زیرا کاری برای وجود ندارد.

اگر هنوز کاربری وارد نشده باشد، یک URL ساخته شده از issuer،clientId ، redirectUrl و state properties از فایل env. ایجاد می‌کند. این باعث فراخوانی v1/authorize/ از تأیید نقطه پایان URL issuer است. سپس به آن URL هدایت می‌شود که یک صفحه ورود به میزبانی Okta است. مانند زمانی که برای تایید هویت به Google هدایت می‌شوید، عمل می‌کند. URL که هنگام ورود به سیستم فراخوانی می‌شود، آدرس http://localhost:3000/auth/callback است که در فایل env. وجود دارد. من همچنین URL اصلی را که کاربر به هنگام رفتن به پارامتر query state هدایت می‌شود، نشان گذاری کرده‌ام. این کار باعث می‌شود بعد از ورود به سیستم، کاربر را به راحتی هدایت کنید.

در مرحله بعدی شما باید مسیر auth/callback را پیاده سازی کنید تا ورود به سیستم را کنترل کنید و کد مجوز دریافتی از Okta را مبادله کنید. فایلی به نام authcontroller.ts در پوشه controllers با محتویات زیر ایجاد کنید:

import { Router } from 'https://deno.land/x/opine@0.12.0/mod.ts';
import { config } from "https://deno.land/x/dotenv/mod.ts";

const auth = new Router();

// users routes
auth.get('/callback', async (req, res) => {
 const { issuer, clientId, clientSecret, redirectUrl, state } = config();

 if (req.query.state.split(':')[0] !== state) {
   res.send('State code does not match.').sendStatus(400);
 }

 const tokenUrl: string = `${issuer}/v1/token`;
 const code: string = req.query.code;

 const headers = new Headers();
 headers.append('Accept', 'application/json');
 headers.append('Authorization', `Basic ${btoa(clientId + ':' + clientSecret)}`);
 headers.append('Content-Type', 'application/x-www-form-urlencoded');

 const response = await fetch(tokenUrl, {
   method: 'POST',
   headers: headers,
   body: `grant_type=authorization_code&redirect_uri=${encodeURIComponent(redirectUrl)}&code=${code}`
 });

 const data = await response.json();
 if (response.status !== 200) {
   res.send(data);
 }
 const user = parseJwt(data.id_token);
 req.app.locals.user = user;
 req.app.locals.isAuthenticated = true;
 res.location(req.query.state.split(':')_[_1] || '/').sendStatus(302);
});


function parseJwt (token:string) {
  const base64Url = token.split('.')_[_1];
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
      return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));

  return JSON.parse(jsonPayload);
};

export default auth;

در واقع اینجا آنچه که تصور می‌کنید اتفاق نمی‌افتد. ابتدا Router را از Opine ایمپورت می‌کند و دوباره فایل env. را می‌خواند. سپس آنها روتر را مانند فایل usercontroller.ts نمونه برداری می‌کنند. مورد بعدی که انجام می‌گیرد تجزیه و تحلیل ساختار پیکربندی برای سهولت استفاده از مقادیر است. بعد پارامتر query state بررسی می‌شود تا از مطابقت آن اطمینان حاصل شود. این به شما کمک می‌کند تا Okta شخصی باشد که کد اجازه را ارسال کرده است. سپس کد مجوز از رشته جستجو با req.query.code خارج می‌شود.

آنچه بعد اتفاق می‌افتد، فراخوانی توکن است. کد تعویض را در یک درخواست POST به Okta ارسال می‌کنید تا با توکن ID عوض شود. بنابراین من در اینجا برخی از عناوین درخواست را ایجاد کرده‌ام. مهمترین چیز هدر Authorization است که دارای مقدار
Basic {yourClientId}:{yourClientSecret} است و Client ID و Client Secret با base64 رمزگذاری شده است. سپس با فراخوانی POST سرانجام آن هدرها و بدنه‌ای با grant_type - همان url وب قبلی - و کد مجوزی که قبلا از Okta دریافت کردید، به نقطه پایانی توکن ارسال می‌شود.

فراخوانی ()fetch،  promise را که با تابع ()then حل شده است برمی‌گرداند. من مقدار JSON شی response را دریافت می‌کنم، از موفقیت آمیز بودن آن اطمینان حاصل می‌کنم، مقدار id_token را با تابع ()parseJwt در زیر تجزیه کرده و در یک متغیر محلی به نام user قرار می‌دهم. در نهایت کاربر قبل از اینکه برای احراز هویت هدایت شود، به همان URL که در ابتدا درخواست کرده بود ارسال می‌شود.

برنامه Deno را اجرا کنید

اکنون می‌توانید دوباره برنامه را از ترمینال با استفاده از دستور زیر اجرا کنید:

deno run -A index.ts

 

پس از اجرا، قادر خواهید بود روی لینک view در صفحه اصلی کلیک کرده و به صفحه ورود میزبان Okta هدایت شوید. پس از ورود به سیستم، به صفحه view هدایت خواهید شد و مشخصات و شناسه خود را در یک لیست مشاهده خواهید کرد.

منبع

گردآوری و تالیف عرفان حشمتی
آفلاین
user-avatar

مهندس معماری سیستم های کامپیوتری، برنامه نویس و طراح وب سایت، علاقه مند به دنیای آی تی و تکنولوژی.

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید