آزمایشی · راهنمای توسعه‌دهندگان

Auth Push آزمایشی — راهنمای توسعه و اتصال

آموزش قدم‌به‌قدم اتصال صفحه ورود به Pushfa.Auth؛ از گرفتن شناسه دستگاه تا انتظار برای تأیید، بررسی کد و رفتن به روش پیامک.

هر بخش از سیستم چه کاری انجام می‌دهد؟

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

Auth Push هنوز آزمایشی است. جریان پیامک فعلی را حذف نکنید و برای خطا، پایان زمان یا نبود دستگاه مورد اعتماد مسیر جایگزین داشته باشید.
بخشکار اصلیچه چیزی نباید در آن باشد؟
مرورگر یا Frontend گرفتن شناسه دستگاه، نمایش انتظار و فرستادن نتیجه برای سرور خود سایت کلید خصوصی یا امکان انتخاب هویت واقعی کاربر
Pushfa SDK بررسی وضعیت، کنترل کد شش‌رقمی و لغو درخواست ساخت نشست ورود سایت
سرور سایت پیداکردن کاربر، ساخت درخواست، بررسی مجوز نهایی و ورود کاربر فرستادن کلید خصوصی برای مرورگر

چند واژه فنی به زبان ساده

واژهمعنی ساده
Subscriber ID شناسه پایدار یک مرورگر یا دستگاه در پوشفا
Challenge درخواست ورود کوتاه‌مدتی که منتظر تأیید است
client_token کلید موقت مرورگر برای دیدن وضعیت یا لغو همان درخواست
Grant مجوز یک‌بارمصرفی که پس از تأیید کاربر صادر می‌شود
Fallback رفتن به روش جایگزین، مانند پیامک، وقتی Push قابل استفاده نیست

۱. گرفتن شناسه دستگاه در مرورگر

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

متد prepare فقط شناسه دستگاه را آماده می‌کند و پنجره اجازه اعلان را نشان نمی‌دهد. برای دریافت درخواست ورود، اعلان‌های آن دستگاه باید از قبل فعال باشند.
// این کد فقط بعد از ورود معتبر کاربر اجرا شود.
await window.Pushfa.requestPermission();
const subscriberId = await window.Pushfa.Auth.prepare();

await fetch('/account/trusted-device', {
  method: 'POST',
  credentials: 'same-origin',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
  },
  body: JSON.stringify({ subscriber_id: subscriberId })
});

مسیرهای داخلی مورد نیاز در سایت شما

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

مسیر نمونهچه چیزی از مرورگر می‌گیرد؟چه چیزی برمی‌گرداند؟
POST /auth/push/start username، requester subscriber و mode challenge_id، client_token، expires_at یا fallback_required
POST /auth/push/complete challenge_id و Grant نتیجه ورود و redirect امن
POST /auth/sms/start اطلاعات لازم fallback شروع OTP پیامکی پس از لغو Push Challenge

۴. انتظار برای تأیید روی دستگاه دیگر

مرورگر شناسه درخواست، توکن کوتاه‌مدت و زمان پایان را از سرور سایت می‌گیرد. SDK هر چند ثانیه وضعیت را بررسی می‌کند. اگر کاربر درخواست را تأیید کند، یک مجوز یک‌بارمصرف به نام Grant برگردانده می‌شود تا سرور ورود را کامل کند.

client_token فقط برای دیدن وضعیت یا لغو همان درخواست است. آن را در URL، ابزار آمارگیری، لاگ یا حافظه دائمی مرورگر ذخیره نکنید.
async function startAuthPushLogin() {
  const requesterSubscriberId = await window.Pushfa.Auth.prepare();
  const startResponse = await fetch('/auth/push/start', {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify({
      username: document.querySelector('#username').value,
      subscriber_id: requesterSubscriberId,
      mode: 'approval'
    })
  });

  if (startResponse.status === 422) {
    return fallbackToSms(null);
  }

  const challenge = await startResponse.json();
  const result = await window.Pushfa.Auth.wait({
    challengeId: challenge.challenge_id,
    clientToken: challenge.client_token,
    expiresAt: challenge.expires_at,
    interval: 2000
  });

  if (result.status === 'approved' && result.grant) {
    return completeLogin(challenge.challenge_id, result.grant);
  }

  return fallbackToSms(challenge);
}

۵. بررسی کد شش‌رقمی Push

در روش otp، یک کد شش‌رقمی در اعلان دستگاه مورد اعتماد نمایش داده می‌شود. کاربر کد را در صفحه ورود جدید وارد می‌کند. اگر کد درست باشد، SDK همان مجوز یک‌بارمصرف لازم برای تکمیل ورود را برمی‌گرداند.

کاربر حداکثر پنج بار می‌تواند کد را وارد کند. پس از آن درخواست رد می‌شود و باید ورود تازه‌ای آغاز شود.
const challenge = await startPushChallenge({ mode: 'otp' });

try {
  const result = await window.Pushfa.Auth.verifyOtp({
    challengeId: challenge.challenge_id,
    clientToken: challenge.client_token
  }, document.querySelector('#push-otp').value);

  if (result.status === 'approved' && result.grant) {
    await completeLogin(challenge.challenge_id, result.grant);
  }
} catch (error) {
  // کد نادرست، Challenge بسته یا تعداد تلاش تمام شده است.
  showOtpError(error.message);
}

وضعیت‌های Challenge

statusمعنااقدام پیشنهادی
pending منتظر OTP یا پاسخ دستگاه دیگر Polling را تا expires_at ادامه دهید و گزینه پیامک را در دسترس نگه دارید.
approved تأیید انجام شده و Grant آماده است Grant را فوراً به Backend خودتان بدهید و مصرف کنید.
rejected کاربر درخواست را رد کرده، ارسال شکست خورده یا تلاش OTP تمام شده است Challenge را دوباره استفاده نکنید؛ پیامک یا شروع مجدد را پیشنهاد دهید.
expired زمان Challenge پایان یافته است Challenge جدید بسازید یا fallback را اجرا کنید.
cancelled درخواست‌کننده، مدیر، سرویس یا دستگاه آن را لغو کرده است هیچ Grant یا لینک قبلی معتبر نیست.
consumed Grant قبلاً مصرف شده است مصرف دوباره رد می‌شود؛ Session قبلی را بررسی کنید.

رفتن به روش پیامک و لغو درخواست قبلی

وقتی کاربر منتظر تأیید است، دکمه «ورود با پیامک» را در دسترس نگه دارید. پیش از ارسال پیامک، درخواست Push را با Pushfa.Auth.cancel لغو کنید تا تأیید دیرهنگام باعث ورود هم‌زمان و ناخواسته نشود.

پاسخ 422 همراه fallback_required=true به معنی خرابی سامانه نیست؛ یعنی دستگاه مناسبی پیدا نشده و باید روش جایگزین مانند پیامک اجرا شود.
async function fallbackToSms(challenge) {
  if (challenge) {
    try {
      await window.Pushfa.Auth.cancel({
        challengeId: challenge.challenge_id,
        clientToken: challenge.client_token
      });
    } catch (_) {
      // Challenge ممکن است هم‌زمان منقضی یا بسته شده باشد.
    }
  }

  return fetch('/auth/sms/start', { method: 'POST', credentials: 'same-origin' });
}

نکات پیاده‌سازی رابط ورود

کنترلقاعده
client_token در URL، analytics، log یا localStorage دائمی ذخیره نشود.
Polling فقط تا expires_at ادامه یابد و هنگام ترک صفحه یا انتخاب fallback متوقف شود.
Fallback دکمه پیامک در pending مخفی نشود و پیش از شروع آن Challenge قبلی cancel شود.
وضعیت‌ها برای pending، rejected، expired و cancelled پیام و اقدام بعدی متفاوت نمایش داده شود.
Grant فقط به Backend همان Origin ارسال شود و هرگز مستقیماً از Browser مصرف نشود.
Ctrl+I