ویژگی تصویر

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

  /  Node.js   /  ساخت سیستم احراز هویت با OAuth در Node.js
بنر تبلیغاتی الف
NodeJS - Node.js

OAuth یکی از متداول‌ترین استانداردها برای اعطای دسترسی ایمن بین سرویس‌ها است. در این مطلب به زبان ساده و تخصصی می‌گوییم که OAuth چیست، کدام جریان مناسب است، و چگونه در Node.js یک سیستم احراز هویت مبتنی بر OAuth (مثلاً با Google) پیاده‌سازی کنیم. مثال‌های عملی، نکات امنیتی و مشکلات رایج نیز پوشش داده می‌شوند.

مبانی: نقش‌ها و جریان‌ها در OAuth

در OAuth چهار نقش اصلی وجود دارد: Resource Owner (کاربر)، Client (اپلیکیشن شما)، Authorization Server (سرویسی که اجازه می‌دهد) و Resource Server (API محافظت‌شده). مهم‌ترین جریان‌ها (grant types):

  • Authorization Code (با PKCE برای اپ‌های موبایل/SPA)
  • Implicit (منسوخ و ناامن؛ از Authorization Code+PKCE استفاده کنید)
  • Client Credentials (برای سرور-به-سرور بدون کاربر)
  • Device Code (برای دیوایس‌های با ورود محدود)

انتخاب جریان مناسب

برای اکثر اپلیکیشن‌های وب و موبایل، Authorization Code با PKCE توصیه می‌شود. برای سرورهای بک‌اند که درخواست‌ها را بدون کاربر انجام می‌دهند، از Client Credentials استفاده کنید.

نمونه عملی: احراز هویت با Google در Node.js با Passport

در این بخش یک پیاده‌سازی ساده با Express و passport-google-oauth20 نشان می‌دهیم. این روش برای ورود با حساب Google مناسب است.

// app.js
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const jwt = require('jsonwebtoken');

passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
  // در اینجا کاربر را در DB پیدا یا ایجاد کنید
  const user = { id: profile.id, displayName: profile.displayName, emails: profile.emails };
  return done(null, user);
}));

passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));

const app = express();
app.use(session({ secret: process.env.SESSION_SECRET || 'keyboard cat', resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));

app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    // پس از موفقیت، یک JWT صادر کنیم و به کلاینت برگردانیم
    const token = jwt.sign({ id: req.user.id, name: req.user.displayName }, process.env.JWT_SECRET || 'jwt_secret', { expiresIn: '1h' });
    res.json({ token, user: req.user });
  }
);

app.get('/protected', (req, res) => {
  const auth = req.headers.authorization;
  if (!auth) return res.status(401).send('No token');
  const token = auth.split(' ')[1];
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET || 'jwt_secret');
    res.send(`Hello ${payload.name}`);
  } catch (e) {
    res.status(401).send('Invalid token');
  }
});

app.listen(3000, () => console.log('Server started on 3000'));

توضیح: این کد یک اپ ساده Express راه‌اندازی می‌کند که از Passport برای پروسه OAuth با Google استفاده می‌کند. در استراتژی Google اطلاعات کاربر را می‌گیرد، سپس در callback پس از ورود موفق یک JWT صادر می‌کند. مسیر /protected با JWT محافظت شده است. در محیط واقعی حتماً مقادیر راز (CLIENT_SECRET و JWT_SECRET) را در متغیر محیطی نگه دارید و از HTTPS استفاده کنید.

توضیح گام‌به‌گام نقشه عملی

  • ثبت اپلیکیشن در Google Console و تنظیم Redirect URI
  • نصب پکیج‌ها: express, passport, passport-google-oauth20, jsonwebtoken, express-session
  • پیاده‌سازی استراتژی Passport و مدیریت session/serialization
  • درخواست Token و تولید JWT محلی برای استفاده در APIها

مثال: استفاده از OpenID Connect و PKCE (با openid-client)

// oidc-client.js
const { Issuer, generators } = require('openid-client');
const express = require('express');

(async () => {
  const issuer = await Issuer.discover('https://accounts.google.com');
  const client = new issuer.Client({
    client_id: process.env.GOOGLE_CLIENT_ID,
    client_secret: process.env.GOOGLE_CLIENT_SECRET,
    redirect_uris: ['http://localhost:3000/oidc/callback'],
    response_types: ['code']
  });

  const app = express();

  app.get('/oidc/auth', (req, res) => {
    const code_verifier = generators.codeVerifier();
    const code_challenge = generators.codeChallenge(code_verifier);
    // ذخیره code_verifier در session یا DB برای اعتبارسنجی بعدی
    req.session = { code_verifier };
    const url = client.authorizationUrl({
      scope: 'openid email profile',
      code_challenge,
      code_challenge_method: 'S256',
      state: 'some-random-state'
    });
    res.redirect(url);
  });

  app.get('/oidc/callback', async (req, res) => {
    const params = client.callbackParams(req);
    const tokenSet = await client.callback('http://localhost:3000/oidc/callback', params, { code_verifier: req.session.code_verifier });
    const userinfo = await client.userinfo(tokenSet.access_token);
    res.json({ tokenSet, userinfo });
  });

  app.listen(3000);
})();

توضیح: این نمونه از OpenID Connect با PKCE استفاده می‌کند. ابتدا اطلاعات مربوط به Identity Provider کشف می‌شود، سپس یک Authorization URL ساخته می‌شود که شامل code_challenge است. بعد از بازگشت کاربر، با code_verifier تبادل انجام شده و اطلاعات کاربر استخراج می‌شود — این روش برای SPA و موبایل امن‌تر از implicit است.

جدول مقایسه جریان‌ها

جریانکاربردامنیت
Authorization Code + PKCEوب، موبایل، SPAبسیار خوب
Client Credentialsسرور-به-سرورخوب (بدون کاربر)
Implicitقدیمی (منسوخ)ضعیف — استفاده نشود

بهترین شیوه‌ها و نکات امنیتی

  • همیشه از HTTPS استفاده کنید.
  • برای اپ‌های عمومی (mobile/SPA) از PKCE استفاده کنید.
  • از state برای جلوگیری از CSRF استفاده کنید و آن را اعتبارسنجی کنید.
  • اسکوپ‌ها را محدود نگه دارید و فقط اطلاعات لازم را درخواست کنید.
  • توکن‌های حساس را در secure HTTP-only cookies یا حافظه امن ذخیره کنید—هرگز در localStorage بدون احتیاط.
  • Refresh tokenها را فقط در جاهایی نگه دارید که ایمن است و امکان ری‌ووک وجود داشته باشد.
  • ID Token یا JWT دریافتی را اعتبارسنجی (signature، issuer، audience، expiry) کنید.

مشکلات رایج و رفع

  • Redirect URI mismatch: آدرس بازگشتی دقیقاً باید در Provider ثبت شده باشد.
  • CORS errors: درخواست‌های بین‌دامنه باید در سرور provider پشتیبانی شوند یا از سمت سرور خودتان proxied شوند.
  • Token expiration: از refresh token یا مکانیزم re-auth استفاده کنید.
  • Logout و ری‌ووک توکن: پیاده‌سازی endpoint ری‌ووک یا استفاده از revocation endpoint سرویس‌دهنده.

نتیجه‌گیری

OAuth ابزار قدرتمندی برای احراز هویت و مجوزدهی است. در Node.js با استفاده از کتابخانه‌های استاندارد مانند Passport یا openid-client می‌توانید پیاده‌سازی‌ای امن و قابل توسعه داشته باشید. انتخاب صحیح جریان (مخصوصاً Authorization Code + PKCE)، پیروی از بهترین شیوه‌های امنیتی و مدیریت ایمن توکن‌ها، کلیدهای موفقیت در پروژه‌های واقعی هستند.

آیا این مطلب برای شما مفید بود ؟

خیر
بله
موضوعات شما در انجمن: