احراز هویت دو مرحله ای یا 2FA مانند داشتن یک قفل اضافی روی درب حساب های آنلاین شما است. 2FA به جای استفاده از رمز عبور، لایه دیگری از امنیت را اضافه می کند. کمی شبیه این است که برای باز کردن یک خزانه به یک کلید و یک کد خاص نیاز دارید.

به آن به عنوان یک سپر برای حساب های خود فکر کنید. گذرواژه‌ها را گاهی می‌توان حدس زد یا دزدید، اما با 2FA، حتی اگر شخصی رمز عبور شما را دریافت کند، همچنان به آن کد یا دستگاه اضافی برای ورود نیاز دارد. این یک مرحله اضافی است که نفوذ حساب‌های شما را برای هکرها سخت‌تر می‌کند.

بنابراین، بیایید نحوه تنظیم این لایه حفاظتی اضافی را با استفاده از PyOTP و Google Authenticator در برنامه Flask خود بررسی کنیم.

فهرست مطالب:

  1. مروری بر PyOTP و Google Authenticator
  2. گردش کار احراز هویت دو مرحله ای در برنامه ما
  3. پیش نیازها
  4. ابزارهای خود را آماده کنید
  5. نحوه راه اندازی پروژه
  6. نحوه ایجاد طرح‌های اولیه برای حساب‌ها و هسته
  7. نحوه ایجاد یک مدل کاربری
  8. نحوه اضافه کردن Flask-Login
  9. نحوه اضافه کردن قالب ها و فایل های استاتیک
  10. نحوه ایجاد صفحه اصلی
  11. نحوه اجرای ثبت نام کاربر
  12. نحوه پیاده سازی ورود کاربر
  13. نحوه خروج کاربران
  14. نحوه اضافه کردن صفحه Setup 2FA
  15. چگونه یک صفحه تأیید 2FA اضافه کنیم
  16. نحوه اجرای برنامه تکمیل شده برای اولین بار
  17. بسته بندی

مروری بر PyOTP و Google Authenticator

PyOTP یک کتابخانه پایتون است که برای تولید گذرواژه های یک بار مصرف مبتنی بر زمان (TOTP) و گذرواژه های یک بار مصرف مبتنی بر HMAC (HOTP) فوق العاده مفید است. نقش اصلی آن حول محور ایجاد این کدهای منحصر به فرد و حساس به زمان است که یک لایه امنیتی اضافی را به حساب های کاربری اضافه می کند.

با ادغام PyOTP در برنامه Flask خود، می توانید به راحتی احراز هویت دو مرحله ای (2FA) را با تولید و تأیید این OTP ها پیاده سازی کنید.

اگر با PyOTP تازه کار هستید یا می خواهید در مورد عملکردهای آن اطلاعات تازه ای کسب کنید، توصیه می کنم راهنمای قبلی من در مورد PyOTP را مرور کنید. این درک مفید خواهد بود زیرا ما به یکپارچه سازی PyOTP در برنامه Flask شما برای احراز هویت دو مرحله ای (2FA) وارد می شویم.

از سوی دیگر، Google Authenticator به عنوان یکی از پرکاربردترین برنامه‌های تولیدکننده OTP در دسترس است. این به عنوان یک پلت فرم امن برای تولید OTP های مبتنی بر زمان، سازگار با سرویس ها و برنامه های مختلف که از 2FA پشتیبانی می کنند، عمل می کند. کاربران می توانند به راحتی Google Authenticator را در دستگاه های خود راه اندازی کنند تا این کدهای حساس به زمان را تولید کنند و سطح امنیتی بیشتری را به حساب های خود اضافه کنند.

گردش کار احراز هویت دو مرحله ای در برنامه ما

در اینجا خلاصه ای از جریان احراز هویت دو مرحله ای در برنامه ما آمده است:

  1. ثبت نام با 2FA Setup: هنگامی که کاربران در وب سایت ما ثبت نام می کنند، از آنها خواسته می شود که یک لایه امنیتی اضافی را تنظیم کنند – 2FA. این شامل اسکن یک کد QR با استفاده از یک برنامه احراز هویت، مانند Google Authenticator، برای پیوند دادن ایمن حساب آنها است.
  2. شروع ورود: هنگامی که کاربران برای ورود به سیستم باز می گردند، با وارد کردن ایمیل/نام کاربری و رمز عبور معمول خود برای دسترسی به حساب خود شروع می کنند.
  3. بررسی امنیتی اضافی: قبل از اعطای دسترسی، وب‌سایت ما با یک مانع دیگر روبرو می‌شود: کاربران باید یک OTP (گذرواژه یک‌بار مصرف) نمایش داده شده در برنامه احراز هویت خود ارائه دهند. این تضمین می کند که آنها نه تنها رمز عبور را وارد می کنند، بلکه هویت خود را با یک کد منحصر به فرد و حساس به زمان تأیید می کنند.
  4. اعتبار سنجی و مجوز: کاربر OTP دریافتی را به پلتفرم ما وارد می کند. سپس سیستم این OTP را در برابر کد مورد انتظار دوبار بررسی می کند و اطلاعات را تأیید می کند. اگر OTP مطابقت داشته باشد، مانند دست دادن مخفی است که به کاربر اجازه دسترسی به حساب خود را می دهد.

این رفت و آمد یکپارچه بین گذرواژه‌ها، برنامه‌های احراز هویت و کدهای منحصربه‌فرد تضمین می‌کند که فقط صاحب حساب واقعی می‌تواند به محتوای ارزشمند پشت درهای دیجیتال وب‌سایت شما دسترسی داشته باشد.

اگر شما نیز از یادگیری بصری لذت می برید، در اینجا یک ویدیوی فانتزی وجود دارد که نشان می دهد برنامه چگونه کار خود را انجام می دهد.

حالا بریم سراغ کد نویسی!

پیش نیازها

قبل از شروع آموزش، مطمئن شوید که شرایط زیر را برآورده کرده اید:

  • دانش کار پایتون
  • پایتون 3.8+ روی سیستم شما نصب شده است
  • دانش اولیه Flask و Flask Blueprints
  • آشنایی با احراز هویت اولیه در Flask (اختیاری)

ابزارهای خود را آماده کنید

برای این پروژه به چند کتابخانه خارجی نیاز دارید. بیایید در مورد آنها بیشتر بدانیم و آنها را یکی یکی نصب کنیم.

اما قبل از نصب آنها، اجازه دهید یک محیط مجازی ایجاد کرده و آن را فعال کنیم.

ابتدا با ایجاد دایرکتوری پروژه و پیمایش به آن به صورت زیر شروع کنید:

mkdir flask-two-factor-auth
ccd flask-two-factor-auth

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

python -m venv env

دستور بالا یک محیط مجازی به نام env ایجاد می کند. حال باید محیط را با استفاده از این دستور فعال کنیم:

source env/Scripts/activate

برای بررسی اینکه آیا محیط فعال شده است یا خیر، می توانید ببینید (env) در ترمینال شما اکنون می توانیم کتابخانه ها را نصب کنیم.

  • Flask یک میکروفریمورک ساده و آسان برای پایتون است که به شما کمک می کند تا برنامه های وب مقیاس پذیر و ایمن بسازید.
  • Flask-Login مدیریت جلسات کاربر را برای Flask فراهم می کند. وظایف متداول ورود به سیستم، خروج از سیستم، و به خاطر سپردن جلسات کاربران شما در مدت زمان طولانی را انجام می دهد.
  • Flask-Bcrypt یک افزونه Flask است که ابزارهای هش bcrypt را برای برنامه شما فراهم می کند.
  • Flask-WTF یک ادغام ساده از Flask و WTForms است که به شما کمک می کند فرم هایی را در Flask ایجاد کنید.
  • Flask-Migrate یک برنامه افزودنی است که انتقال پایگاه داده SQLAlchemy را برای برنامه های Flask با استفاده از Alembic انجام می دهد. عملیات پایگاه داده از طریق رابط خط فرمان Flask در دسترس است.
  • Flask-SQLAlchemy یک افزونه برای Flask است که پشتیبانی از SQLAlchemy را به برنامه شما اضافه می کند. این به شما کمک می کند تا کارها را با استفاده از SQLAlchemy با Flask با ارائه پیش فرض های مفید و کمک های اضافی که انجام کارهای معمول را آسان تر می کند، ساده کنید.
  • PyOTP به شما کمک می کند OTP ها را با استفاده از الگوریتم های OTP مبتنی بر زمان (TOTP) و OTP مبتنی بر HMAC (HOTP) بدون زحمت تولید کنید.
  • QRCode به شما کمک می کند تا کدهای QR را در پایتون ایجاد کنید
  • Python Decouple به شما کمک می کند از متغیرهای محیطی در پروژه پایتون خود استفاده کنید.

برای نصب کتابخانه های فوق به صورت یکجا، دستور زیر را اجرا کنید:

pip install Flask Flask-Login Flask-Bcrypt Flask-WTF FLask-Migrate Flask-SQLAlchemy pyotp qrcode python-decouple

نحوه راه اندازی پروژه

بیایید با ایجاد یک شروع کنیم src فهرست راهنما:

mkdir src

فایل اول خواهد بود __init__.py فایل برای پروژه:

from decouple import config
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object(config("APP_SETTINGS"))

bcrypt = Bcrypt(app)
db = SQLAlchemy(app)
migrate = Migrate(app, db)

# Registering blueprints
from src.accounts.views import accounts_bp
from src.core.views import core_bp

app.register_blueprint(accounts_bp)
app.register_blueprint(core_bp)

در اسکریپت بالا یک اپلیکیشن Flask به نام ایجاد کردیم app . ما استفاده می کنیم __name__ آرگومان برای نشان دادن ماژول یا بسته برنامه به طوری که Flask بداند فایل های دیگری مانند الگوها را کجا پیدا کند. ما همچنین پیکربندی برنامه را با استفاده از یک متغیر محیطی به نام تنظیم کردیم APP_SETTINGS. بعدا صادرش میکنیم

برای استفاده از Flask-Bcrypt، Flask-SQLAlchemy و Flask-Migrate در برنامه خود، فقط باید اشیاء را ایجاد کنیم. Bcrypt، SQLAlchemy و Migrate کلاس ها از flask_bcrypt، flask_sqlalchemy و flask_migrate کتابخانه ها به ترتیب

ما همچنین طرح‌هایی را به نام ثبت کرده‌ایم accounts_bp و core_bp در برنامه. بعداً در آموزش آنها را تعریف خواهیم کرد.

در دایرکتوری اصلی پروژه (یعنی خارج از src دایرکتوری)، فایلی به نام ایجاد کنید config.py. ما تنظیمات پروژه را در این فایل ذخیره می کنیم. در داخل فایل، محتوای زیر را اضافه کنید:

from decouple import config

DATABASE_URI = config("DATABASE_URL")
if DATABASE_URI.startswith("postgres://"):
    DATABASE_URI = DATABASE_URI.replace("postgres://", "postgresql://", 1)


class Config(object):
    DEBUG = False
    TESTING = False
    CSRF_ENABLED = True
    SECRET_KEY = config("SECRET_KEY", default="guess-me")
    SQLALCHEMY_DATABASE_URI = DATABASE_URI
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    BCRYPT_LOG_ROUNDS = 13
    WTF_CSRF_ENABLED = True
    DEBUG_TB_ENABLED = False
    DEBUG_TB_INTERCEPT_REDIRECTS = False
    APP_NAME = config("APP_NAME")


class DevelopmentConfig(Config):
    DEVELOPMENT = True
    DEBUG = True
    WTF_CSRF_ENABLED = False
    DEBUG_TB_ENABLED = True


class TestingConfig(Config):
    TESTING = True
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///testdb.sqlite"
    BCRYPT_LOG_ROUNDS = 1
    WTF_CSRF_ENABLED = False


class ProductionConfig(Config):
    DEBUG = False
    DEBUG_TB_ENABLED = False

در اسکریپت فوق، a را ایجاد کرده ایم Config کلاس و مشخصه های مختلفی را درون آن تعریف کرد. همچنین، ما کلاس های مختلف کودک (بر اساس مراحل مختلف رشد) ایجاد کرده ایم که به ارث می برند Config کلاس

توجه داشته باشید که ما از چند متغیر محیطی مانند SECRET_KEY، DATABASE_URL، و APP_NAME. یک فایل به نام ایجاد کنید .env در دایرکتوری ریشه و محتوای زیر را در آنجا اضافه کنید:

export SECRET_KEY=fdkjshfhjsdfdskfdsfdcbsjdkfdsdf
export DEBUG=True
export APP_SETTINGS=config.DevelopmentConfig
export DATABASE_URL=sqlite:///db.sqlite
export FLASK_APP=src
export FLASK_DEBUG=1
export APP_NAME="Flask User Authentication App"

جدای از SECRET_KEY ، DATABASE_URL و APP_NAME، ما نیز صادر کرده ایم APP_SETTINGS، DEBUG، FLASK_APP، و FLASK_DEBUG.

این APP_SETTINGS به یکی از کلاس هایی که در آن ایجاد کردیم اشاره دارد config.py فایل. ما آن را در مرحله فعلی پروژه قرار دادیم.

ارزش FLASK_APP نام بسته ای است که ما ایجاد کرده ایم. از آنجایی که برنامه در مرحله توسعه است، می توانید مقادیر آن را تنظیم کنید DEBUG و FLASK_DEBUG به True و 1، به ترتیب.

دستور زیر را اجرا کنید تا تمام متغیرهای محیطی را از آن صادر کنید .env فایل:

source .env

در مرحله بعد، ما یک برنامه CLI از برنامه ایجاد می کنیم تا بعداً بتوانیم در صورت نیاز دستورات سفارشی را اضافه کنیم.

ایجاد یک manage.py فایل را در دایرکتوری اصلی برنامه قرار دهید و کد زیر را اضافه کنید:

from flask.cli import FlaskGroup

from src import app

cli = FlaskGroup(app)


if __name__ == "__main__":
    cli()

اکنون، برنامه اصلی شما آماده است. با استفاده از دستور زیر می توانید آن را اجرا کنید:

python manage.py run

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

flask-two-factor-auth/
├── src/
│   └── __init__.py
├── .env
├── config.py
└── manage.py

نحوه ایجاد طرح‌های اولیه برای حساب‌ها و هسته

همانطور که قبلا ذکر شد، شما از مفاهیم طرح های اولیه در پروژه استفاده خواهید کرد. بیایید دو طرح ایجاد کنیم – accounts_bp و core_bp – در این بخش.

ابتدا یک دایرکتوری به نام ایجاد کنید accounts مثل این:

mkdir accounts
cd accounts

بعد، یک خالی اضافه کنید __init__.py فایل را برای مخفی کردن آن در یک بسته پایتون قرار دهید. حال، یک را ایجاد کنید views.py فایل داخل بسته را که در آن تمام مسیرهای مربوط به احراز هویت کاربر را ذخیره خواهید کرد.

touch __init__.py views.py

کد زیر را داخل آن اضافه کنید views.py فایل:

from flask import Blueprint

accounts_bp = Blueprint("accounts", __name__)

در اسکریپت بالا یک طرح اولیه به نام ایجاد کرده اید accounts_bp برای accounts بسته بندی

به طور مشابه، شما می توانید یک ایجاد کنید core را در پوشه ریشه بسته بندی کنید و a را اضافه کنید views.py فایل.

mkdir core
cd core
touch __init__.py views.py

حالا کد زیر را داخل آن اضافه کنید views.py فایل:

from flask import Blueprint

core_bp = Blueprint("core", __name__)

توجه: اگر با Flask Blueprints تازه کار هستید، حتماً این آموزش را دنبال کنید تا در مورد نحوه عملکرد آن بیشتر بدانید.

اکنون، ساختار فایل شما باید شبیه آنچه در زیر می بینید باشد:

flask-two-factor-auth/
├── src/
│   ├── accounts/
│   │   ├── __init__.py
│   │   └── views.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── views.py
│   └── __init__.py
├── .env
├── config.py
└── manage.py

نحوه ایجاد یک مدل کاربری

بیایید a ایجاد کنیم models.py فایل داخل accounts بسته بندی

touch src/accounts/models.py

درون models.py فایل، کد زیر را اضافه کنید:

from datetime import datetime

import pyotp
from flask_login import UserMixin

from src import bcrypt, db
from config import Config


class User(db.Model):

    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, unique=True, nullable=False)
    password = db.Column(db.String, nullable=False)
    created_at = db.Column(db.DateTime, nullable=False)
    is_two_factor_authentication_enabled = db.Column(
        db.Boolean, nullable=False, default=False)
    secret_token = db.Column(db.String, unique=True)

    def __init__(self, username, password):
        self.username = username
        self.password = bcrypt.generate_password_hash(password)
        self.created_at = datetime.now()
        self.secret_token = pyotp.random_base32()

    def get_authentication_setup_uri(self):
        return pyotp.totp.TOTP(self.secret_token).provisioning_uri(
            name=self.username, issuer_name=Config.APP_NAME)

    def is_otp_valid(self, user_otp):
        totp = pyotp.parse_uri(self.get_authentication_setup_uri())
        return totp.verify(user_otp)

    def __repr__(self):
        return f"<user {self.username}>"

در کد بالا یک را ایجاد کردید User مدل با ارث بردن db.Model کلاس این User مدل از فیلدهای زیر تشکیل شده است:

  • id: کلید اصلی را برای users جدول
  • username: نام کاربری کاربر را ذخیره می کند
  • password: رمز عبور هش شده کاربر را ذخیره می کند
  • created_at: زمان ایجاد کاربر را ذخیره می کند
  • is_two_factor_authentication_enabled: پرچم بولین که ذخیره می کند آیا کاربر احراز هویت دو مرحله ای را فعال کرده است یا خیر. مقدار پیش فرض است False.
  • secret_token: یک توکن منحصر به فرد تولید شده برای هر کاربر را ذخیره می کند که برای اجرای احراز هویت دو مرحله ای ضروری است.
پیشنهاد می‌کنیم بخوانید:  نمودارها در پایتون - تئوری و پیاده سازی - حداقل درختان پوشا

سازنده مقدار را مقداردهی اولیه می کند User اعتراض به مصداق با قبول username و password مولفه های. با استفاده از رمز عبور ارائه شده را هش می کند bcrypt.generate_password_hash(password)، مهر زمانی فعلی را به عنوان علامت ثبت می کند created_at ارزش، و منحصر به فرد ایجاد می کند secret_token استفاده کردن pyotp.random_base32() برای راه اندازی 2FA

این get_authentication_setup_uri() روش یک URI راه‌اندازی ایجاد می‌کند که توسط برنامه‌های احراز هویت مانند Google Authenticator استفاده می‌شود. یک URI حاوی نام کاربری کاربر و نام برنامه (Config.APP_NAME) برای تنظیم احراز هویت دو مرحله ای ضروری است. فرمت اصلی URI این است:

otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

که در آن alice@google.com نام کاربری کاربر و مثال نام برنامه است.

بعدی، is_otp_valid() متد رمز عبور یکبار مصرف (OTP) وارد شده توسط کاربر در هنگام ورود را تأیید می کند. URI راه‌اندازی که قبلاً تولید شده بود را تجزیه می‌کند، اعتبار OTP ارائه شده را بررسی می‌کند (user_otp) و برمی گردد True اگر OTP مطابقت داشته باشد، اطمینان از احراز هویت ایمن.

در نهایت، __repr__ متد یک نمایش رشته ای از User شی، نام کاربری مرتبط را هنگامی که نمونه ای از کلاس چاپ می شود یا به عنوان یک رشته نمایش داده می شود، نمایش می دهد.

نحوه اضافه کردن Flask-Login

مهمترین بخش Flask-Login این است LoginManager کلاسی که به برنامه شما و Flask-Login اجازه می دهد با هم کار کنند.

در src/__init__.py فایل، کد زیر را اضافه کنید:

from decouple import config
from flask import Flask
from flask_login import LoginManager # Add this line
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object(config("APP_SETTINGS"))

login_manager = LoginManager() # Add this line
login_manager.init_app(app) # Add this line
db = SQLAlchemy(app)
migrate = Migrate(app, db)

# Registering blueprints
from src.accounts.views import accounts_bp
from src.core.views import core_bp

app.register_blueprint(accounts_bp)
app.register_blueprint(core_bp)

در اسکریپت بالا، ما لاگین منیجر را در اپلیکیشن خود ایجاد و مقداردهی اولیه کردیم.

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

from src.accounts.models import User

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter(User.id == int(user_id)).first()

این User مدل باید ویژگی ها و روش های زیر را پیاده سازی کند:

  • is_authenticated: اگر کاربر احراز هویت شده باشد، این ویژگی True را برمی گرداند.
  • is_active: اگر این یک کاربر فعال باشد (اکانت فعال شده باشد) این ویژگی True را برمی گرداند.
  • is_anonymous: اگر کاربر ناشناس باشد، این ویژگی True را برمی‌گرداند (کاربران واقعی False را برمی‌گردانند).
  • get_id(): این متد رشته ای را برمی گرداند که به طور منحصر به فرد این کاربر را شناسایی می کند و می تواند برای بارگیری کاربر از user_loader پاسخ به تماس

اکنون، ما نیازی به اجرای صریح اینها نداریم. در عوض، Flask-Login یک را فراهم می کند UserMixin کلاسی که شامل پیاده سازی های پیش فرض برای همه این ویژگی ها و متدها است. ما فقط باید آن را به روش زیر به ارث ببریم:

from datetime import datetime

from flask_login import UserMixin # Add this line

from src import bcrypt, db


class User(UserMixin, db.Model): # Change this line
	....

همچنین می‌توانیم فرآیند ورود پیش‌فرض را در قسمت سفارشی‌سازی کنیم src/__init__.py فایل.

نام نمای ورود به سیستم را می توان به صورت تنظیم کرد LoginManager.login_view. مقدار به نام تابعی اشاره دارد که فرآیند ورود را مدیریت می کند.

login_manager.login_view = "accounts.login"

برای سفارشی کردن دسته پیام، تنظیم کنید LoginManager.login_message_category:

login_manager.login_message_category = "danger"

نحوه اضافه کردن قالب ها و فایل های استاتیک

بیایید یک فایل CSS به نام ایجاد کنیم styles.css درون src/static پوشه:

.error {
  color: red;
  margin-bottom: 5px;
  text-align: center;
}

a {
  text-decoration: none;
}

بیایید قالب های اولیه را نیز در داخل ایجاد کنیم src/templates پوشه ایجاد یک _base.html فایل و کد زیر را اضافه کنید:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Two Factor Authentication</title>
    <!-- meta -->
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <!-- styles -->
    <!-- CSS only -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    <link rel="stylesheet" href="{{url_for('static', filename="styles.css")}}">
    {% block css %}{% endblock %}
  </head>
  <body>

    {% include "navigation.html" %}

    <div class="container">

      <br>

      <!-- messages -->
      {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
      <div class="row">
        <div class="col-md-4"></div>
        <div class="col-md-4">
          {% for category, message in messages %}
          <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
           {{message}}
           <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
          </div>
          {% endfor %}
        </div>
        <div class="col-md-4"></div>
      </div>
      {% endif %}
      {% endwith %}

      <!-- child template -->
      {% block content %}{% endblock %}

    </div>

    <!-- scripts -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <!-- JavaScript Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
    {% block js %}{% endblock %}
  </body>
</html>

این _base.html فایل HTML والد است که توسط سایر قالب ها به ارث می رسد. ما پشتیبانی از Bootstrap 5 را در فایل فوق اضافه کرده ایم. ما همچنین از Flask Flashes برای نشان دادن هشدارهای Bootstrap در برنامه استفاده می کنیم.

بیایید یک را نیز ایجاد کنیم navigation.html فایلی که شامل نوار ناوبری برنامه است:

<!-- Navigation -->
<nav class="navbar bg-dark navbar-expand-lg bg-body-tertiary p-3" data-bs-theme="dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("core.home') }}">Two-Factor Authentication App</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      {% if current_user.is_authenticated %}
      <a href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("accounts.logout') }}"><button type="button" class="btn btn-danger me-2">Logout</button></a>
      {% endif %}
    </div>
  </div>
</nav>

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

نحوه ایجاد صفحه اصلی

در این بخش ابتدا یک تابع view برای صفحه اصلی در داخل ایجاد می کنیم core/views.py فایل. کد زیر را در آنجا اضافه کنید:

from flask import Blueprint, render_template
from flask_login import login_required

core_bp = Blueprint("core", __name__)


@core_bp.route("/")
@login_required
def home():
    return render_template("core/index.html")

توجه داشته باشید که ما از نقشه برای اضافه کردن مسیر استفاده کرده ایم. الف را نیز اضافه کردیم @login_required میان افزار برای جلوگیری از دسترسی کاربران احراز هویت نشده.

بعد، بیایید یک را ایجاد کنیم index.html فایل داخل templates/core پوشه، و کد زیر را اضافه کنید:

{% extends "_base.html" %}
{% block content %}

<h1 class="text-center">Welcome {{current_user.username}}!</h1>

{% endblock %}

صفحه HTML فقط یک پیام خوش آمدگویی برای کاربران احراز هویت شده خواهد داشت.

ساختار فایل شما در حال حاضر باید به شکل زیر باشد:

flask-two-factor-auth/
├── src/
│   ├── accounts/
│   │   ├── __init__.py
│   │   └── views.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── views.py
│   ├── static/
│   │   └── styles.css
│   ├── templates/
│   │   ├── core/
│   │   │   └── index.html
│   │   ├── _base.html
│   │   └── navigation.html
│   └── __init__.py
├── .env
├── config.py
└── manage.py

نحوه اجرای ثبت نام کاربر

اول از همه، ما یک فرم ثبت نام با استفاده از Flask-WTF ایجاد می کنیم. ایجاد یک forms.py فایل داخل accounts بسته بندی کنید و کد زیر را اضافه کنید:

from flask_wtf import FlaskForm
from wtforms import EmailField, PasswordField
from wtforms.validators import DataRequired, Email, EqualTo, Length

from src.accounts.models import User


class RegisterForm(FlaskForm):
    username = StringField(
        "Username", validators=[DataRequired(), Length(min=6, max=40)]
    )
    password = PasswordField(
        "Password", validators=[DataRequired(), Length(min=6, max=25)]
    )
    confirm = PasswordField(
        "Repeat password",
        validators=[
            DataRequired(),
            EqualTo("password", message="Passwords must match."),
        ],
    )

    def validate(self, extra_validators):
        initial_validation = super(RegisterForm, self).validate(extra_validators)
        if not initial_validation:
            return False
        user = User.query.filter_by(username=self.username.data).first()
        if user:
            self.username.errors.append("Username already registered")
            return False
        if self.password.data != self.confirm.data:
            self.password.errors.append("Passwords must match")
            return False
        return True

این RegisterForm گسترش می دهد FlaskForm کلاس و شامل سه فیلد – username، password، و confirm. ما اعتبار سنجی های مختلفی مانند DataRequired، Length، Email، و EqualTo به زمینه های مربوطه

الف را هم تعریف کردیم validate() روشی که هنگام ارسال فرم به طور خودکار فراخوانی می شود.

در داخل متد، ابتدا اعتبار سنجی اولیه ارائه شده توسط FlaskForm را انجام می دهیم. در صورت موفقیت آمیز بودن، ما اعتبارسنجی سفارشی خود را انجام می دهیم، مانند بررسی اینکه آیا کاربر قبلاً ثبت نام کرده است یا خیر، و گذرواژه را با رمز عبور تأیید شده مطابقت می دهیم. در صورت وجود هر گونه خطایی، پیام خطا را در فیلدهای مربوطه اضافه می کنیم.

حالا بیایید از این فرم در داخل فایل HTML استفاده کنیم. ایجاد کنید accounts دایرکتوری داخل templates پوشه و یک فایل جدید به نام اضافه کنید register.html درون آن. کد زیر را اضافه کنید:

{% extends "_base.html" %}

{% block content %}

<div class="row">
  <div class="col-md-4"></div>
  <div class="col-md-4">
    <main class="form-signin w-100 m-auto">
      <form role="form" method="post" action="">
        {{ form.csrf_token }}
        <h1 class="h3 mb-3 fw-normal text-center">Please register</h1>

        <div class="form-floating">
          {{ form.username(placeholder="username", class="form-control mb-2") }}
          {{ form.username.label }}
            {% if form.username.errors %}
              {% for error in form.username.errors %}
                <div class="alert alert-danger" role="alert">
                  {{ error }}
                </div>
              {% endfor %}
            {% endif %}
        </div>
        <div class="form-floating">
          {{ form.password(placeholder="password", class="form-control mb-2") }}
          {{ form.password.label }}
            {% if form.password.errors %}
              {% for error in form.password.errors %}
                <div class="alert alert-danger" role="alert">
                  {{ error }}
                </div>
              {% endfor %}
            {% endif %}
        </div>
        <div class="form-floating">
          {{ form.confirm(placeholder="Confirm Password", class="form-control mb-2") }}
          {{ form.confirm.label }}
            {% if form.confirm.errors %}
              {% for error in form.confirm.errors %}
                <div class="alert alert-danger" role="alert">
                  {{ error }}
                </div>
              {% endfor %}
            {% endif %}
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign up</button>
        <p class="text-center mt-3">Already registered? <a href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("accounts.login') }}">Login now</a></p>
      </form>
    </main>
  </div>
  <div class="col-md-4"></div>
</div>

{% endblock %}

در قالب Jinja بالا، از فرمی که ایجاد کردیم استفاده می‌کنیم و بررسی‌های منطقی رسیدگی به خطاها را برای خطاهای اعتبارسنجی در هر فیلد اضافه می‌کنیم. کاربران می توانند با کلیک بر روی دکمه “ثبت نام” فرم را ارسال کنند و پیوندی در زیر فرم به کاربرانی که قبلا ثبت نام کرده اند اجازه می دهد تا برای احراز هویت به صفحه ورود به سیستم بروید.

بعد، بیایید از این فرم در views.py برای ایجاد یک تابع برای رسیدگی به فرآیند ثبت نام.

from .forms import RegisterForm
from src.accounts.models import User
from src import db, bcrypt
from flask_login import current_user
from flask import Blueprint, flash, redirect, render_template, request, url_for

accounts_bp = Blueprint("accounts", __name__)

HOME_URL = "core.home"
SETUP_2FA_URL = "accounts.setup_two_factor_auth"
VERIFY_2FA_URL = "accounts.verify_two_factor_auth"

@accounts_bp.route("/register", methods=["GET", "POST"])
def register():
    if current_user.is_authenticated:
        if current_user.is_two_factor_authentication_enabled:
            flash("You are already registered.", "info")
            return redirect(url_for(HOME_URL))
        else:
            flash("You have not enabled 2-Factor Authentication. Please enable first to login.", "info")
            return redirect(url_for(SETUP_2FA_URL))
    form = RegisterForm(request.form)
    if form.validate_on_submit():
        try:
            user = User(username=form.username.data, password=form.password.data)
            db.session.add(user)
            db.session.commit()

            login_user(user)
            flash("You are registered. You have to enable 2-Factor Authentication first to login.", "success")

            return redirect(url_for(SETUP_2FA_URL))
        except Exception:
            db.session.rollback()
            flash("Registration failed. Please try again.", "danger")

    return render_template("accounts/register.html", form=form)

مسیر با بررسی اینکه آیا کاربر فعلی قبلاً احراز هویت شده است یا خیر آغاز می شود. اگر چنین است، بررسی می کند که آیا 2FA برای کاربر فعال است یا خیر. اگر 2FA قبلاً فعال باشد، پیامی به کاربر اطلاع می‌دهد که قبلاً ثبت‌نام کرده‌اند و او را به URL خانه هدایت می‌کند. با این حال، اگر کاربر احراز هویت شده باشد اما 2FA فعال نباشد، یک پیام فلش از کاربر می‌خواهد تا قبل از ورود به سیستم، ابتدا 2FA را فعال کرده و آنها را به URL تنظیم 2FA هدایت کند.

اگر کاربر احراز هویت نشده باشد یا هنوز 2FA را ثبت نکرده باشد، کد یک فرم ثبت نام را راه اندازی می کند و پس از ارسال، اعتبار داده های فرم را تأیید می کند. پس از تایید موفقیت آمیز فرم، یک فرم جدید ایجاد می کنیم User با نام کاربری و رمز عبور ارائه شده شیء کنید و آن را در پایگاه داده ذخیره کنید.

پس از ثبت نام موفقیت آمیز کاربر، کاربر تازه ثبت نام شده وارد سیستم می شود. یک پیام موفقیت آمیز چشمک می زند که به کاربر از ثبت نام موفقیت آمیز اطلاع می دهد و از او می خواهد قبل از ورود به سیستم 2FA را فعال کند. متعاقباً، کاربر به URL راه اندازی 2FA هدایت می شود تا 2FA فعال شود.

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

ابتدا بیایید یک فرم ورود به سیستم ایجاد کنیم accounts/forms.py فایل:

class LoginForm(FlaskForm):
    username = StringField("Username", validators=[DataRequired()])
    password = PasswordField("Password", validators=[DataRequired()])

فرم مشابه فرم ثبت نام است اما فقط دو فیلد دارد – username و password.

حالا بیایید از این فرم در داخل فایل HTML جدید به نام استفاده کنیم login.html ایجاد شده در داخل templates/accounts فهرست راهنما. کد زیر را اضافه کنید:

{% extends "_base.html" %}

{% block content %}

<div class="row">
  <div class="col-md-4"></div>
  <div class="col-md-4">
    <main class="form-signin w-100 m-auto">
      <form role="form" method="post" action="">
        {{ form.csrf_token }}
        <h1 class="h3 mb-3 fw-normal text-center">Please sign in</h1>

        <div class="form-floating">
          {{ form.username(placeholder="username", class="form-control mb-2") }}
          {{ form.username.label }}
            {% if form.username.errors %}
              {% for error in form.username.errors %}
              <div class="alert alert-danger" role="alert">
                {{ error }}
              </div>
              {% endfor %}
            {% endif %}
        </div>
        <div class="form-floating">
          {{ form.password(placeholder="password", class="form-control mb-2") }}
          {{ form.password.label }}
            {% if form.password.errors %}
              {% for error in form.password.errors %}
                <div class="alert alert-danger" role="alert">
                  {{ error }}
                </div>
              {% endfor %}
            {% endif %}
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
        <p class="text-center mt-3">New User? <a href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("accounts.register') }}">Register now</a></p>
      </form>
    </main>
  </div>
  <div class="col-md-4"></div>
</div>

{% endblock %}

فایل HTML بالا نیز شبیه به register.html فایل اما تنها با دو فیلد برای نام کاربری و رمز عبور.

پیشنهاد می‌کنیم بخوانید:  ایندکس یک DataFrame پاندا را به یک ستون در پایتون تبدیل کنید

در مرحله بعد، بیایید یک تابع view ایجاد کنیم تا فرآیند ورود به سیستم را انجام دهد accounts/views.py فایل:

from .forms import LoginForm, RegisterForm

@accounts_bp.route("/login", methods=["GET", "POST"])
def login():
    if current_user.is_authenticated:
        if current_user.is_two_factor_authentication_enabled:
            flash("You are already logged in.", "info")
            return redirect(url_for(HOME_URL))
        else:
            flash("You have not enabled 2-Factor Authentication. Please enable first to login.", "info")
            return redirect(url_for(SETUP_2FA_URL))
        
    form = LoginForm(request.form)
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and bcrypt.check_password_hash(user.password, request.form["password"]):
            login_user(user)
            if not current_user.is_two_factor_authentication_enabled:
                flash(
                    "You have not enabled 2-Factor Authentication. Please enable first to login.", "info")
                return redirect(url_for(SETUP_2FA_URL))
            return redirect(url_for(VERIFY_2FA_URL))
        elif not user:
            flash("You are not registered. Please register.", "danger")
        else:
            flash("Invalid username and/or password.", "danger")
    return render_template("accounts/login.html", form=form)

مسیر با بررسی اینکه آیا کاربر فعلی قبلاً احراز هویت شده است شروع می شود. اگر کاربر احراز هویت شده باشد و 2FA فعال باشد، پیامی به کاربر اطلاع می‌دهد که قبلاً وارد شده است و او را به URL خانه هدایت می‌کند. اگر کاربر احراز هویت شده باشد اما 2FA فعال نباشد، یک پیام فلش از کاربر می‌خواهد تا قبل از ورود به سیستم، 2FA را فعال کند و او را به URL تنظیم 2FA هدایت کند.

اگر کاربر احراز هویت نشده باشد، کد یک فرم ورود را راه‌اندازی می‌کند و پس از ارسال، داده‌های فرم را تأیید می‌کند. پس از تأیید موفقیت آمیز، از پایگاه داده درخواست می کند تا کاربری مطابق با نام کاربری ارائه شده را پیدا کند. اگر کاربر وجود داشته باشد و رمز عبور با رمز عبور هش شده ذخیره شده در پایگاه داده مطابقت داشته باشد، کاربر وارد سیستم می شود.

علاوه بر این، اگر 2FA برای کاربر فعلی پس از ورود موفقیت آمیز فعال نباشد، یک پیام فلش از کاربر می خواهد قبل از ادامه، 2FA را فعال کرده و آنها را به URL راه اندازی 2FA هدایت کند. اگر ورود موفقیت آمیز باشد و 2FA فعال باشد، کاربر به URL تأیید 2FA هدایت می شود.

اگر کاربر ثبت نام نکرده باشد، یک پیام فلش به او اطلاع می دهد که ثبت نام کند. در صورت عدم تطابق در نام کاربری یا رمز عبور ارائه شده، یک پیام فلش دیگر کاربر را از اعتبار نامعتبر مطلع می کند.

نحوه خروج کاربران

خروج کاربر یک فرآیند بسیار ساده است. شما فقط باید یک تابع view برای آن در داخل ایجاد کنید accounts/views.py فایل:

from flask_login import login_required, login_user, logout_user


@accounts_bp.route("/logout")
@login_required
def logout():
    logout_user()
    flash("You were logged out.", "success")
    return redirect(url_for("accounts.login"))

این Flask-Login کتابخانه حاوی الف logout_user روشی که کاربر را از جلسه حذف می کند. ما استفاده کردیم @login_required دکوراتور به طوری که فقط کاربران تأیید شده بتوانند از سیستم خارج شوند.

نحوه اضافه کردن صفحه Setup 2FA

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

بیایید با مسیر صفحه شروع کنیم:

from src.utils import get_b64encoded_qr_image

@accounts_bp.route("/setup-2fa")
@login_required
def setup_two_factor_auth():
    secret = current_user.secret_token
    uri = current_user.get_authentication_setup_uri()
    base64_qr_image = get_b64encoded_qr_image(uri)
    return render_template("accounts/setup-2fa.html", secret=secret, qr_image=base64_qr_image)

مسیر، ایجاد شده در داخل accounts/views.py، تضمین می کند که فقط کاربران تأیید شده می توانند با استفاده از آن به آن دسترسی داشته باشند @login_required دکوراتور

با دسترسی به این مسیر، تابع، کاربر فعلی را بازیابی می کند secret_token برای راه اندازی 2FA و ایجاد یک URI از طریق current_user.get_authentication_setup_uri() برای پیکربندی یک برنامه احراز هویت مانند Google Authenticator.

همچنین استفاده می کند get_b64encoded_qr_image(uri) برای به دست آوردن یک تصویر کد QR با کد Base64 که نشان دهنده این URI راه اندازی است. در ادامه به تعریف آن می پردازیم.

در نهایت، آن را رندر می کند setup-2fa.html الگو، عبور از کاربر secret_token و تصویر QR رمزگذاری شده با Base64 را به الگو برای اسکن کردن آن توسط کاربران.

بعد، a ایجاد کنید utils.py فایل در src دایرکتوری و کد زیر را برای تولید QR اضافه کنید:

from io import BytesIO
import qrcode
from base64 import b64encode


def get_b64encoded_qr_image(data):
    print(data)
    qr = qrcode.QRCode(version=1, box_size=10, border=5)
    qr.add_data(data)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    buffered = BytesIO()
    img.save(buffered)
    return b64encode(buffered.getvalue()).decode("utf-8")

یادت باشد qrcode کتابخانه ای که در ابتدای آموزش نصب کردیم؟ این جایی است که ما از آن استفاده خواهیم کرد.

به محض دریافت data به عنوان ورودی، نشان دهنده محتوایی است که باید در کد QR تعبیه شود، تابع یک شی QRCode را با استفاده از qrcode کتابخانه داده های ارائه شده را به این نمونه کد QR اضافه می کند و کد QR را تولید می کند. سپس کد این کد QR را به یک نمایش تصویر تبدیل می کند.

با استفاده از یک شی BytesIO، این تصویر را در حافظه ذخیره می کند. این تابع به کدگذاری محتوای این بافر درون حافظه، که تصویر کد QR را نشان می‌دهد، در قالب Base64 کد می‌کند. در نهایت، این رشته رمزگذاری شده با Base64 را برمی گرداند و تصویر کد QR را محصور می کند و آماده انتقال یا نمایش در برنامه های مختلف است.

بعد، بیایید ایجاد کنیم setup-2fa.html صفحه داخل templates/accounts پوشه، و محتوای زیر را اضافه کنید:

{% extends "_base.html" %}

{% block content %}

<div class="row">
  <div class="col-md-4"></div>
  <div class="col-md-4">
    <main class="form-signin w-100 m-auto">
      <form role="form">
        <h5>Instructions!</h5>
          <ul>
            <li>Download <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en&gl=US" target="_blank">Google Authenticator</a> on your mobile.</li>
            <li>Set up a new authenticator.</li>
            <li>Once you have scanned the QR, please click <a href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("accounts.verify_two_factor_auth') }}">here.</li>
          </ul>
          <div class="text-center">
            <img src="data:image/png;base64, {{ qr_image }}" alt="Secret Token" style="width:200px;height:200px"/>
          </div>
        <div class="form-group">
          <label for="secret">Secret Token</label>
          <input type="text" class="form-control" id="secret" value="{{ secret }}" readonly>
        </div>
        <div class="text-center mt-2">
          <button type="button" class="btn btn-primary" onclick="copySecret()">
            Copy Secret
          </button>
        </div>
        <p class="mt-4 text-center">
          Once you have scanned the QR, please click <a href="https://www.freecodecamp.org/news/how-to-implement-two-factor-authentication-in-your-flask-app/{{ url_for("accounts.verify_two_factor_auth') }}">here</a>.
        </p>
      </form>
    </main>
  </div>
  <div class="col-md-4"></div>
</div>

{% endblock %}

{% block js %}
<script>
    function copySecret() {
    var copyText = document.getElementById("secret");
    copyText.select();
    copyText.setSelectionRange(0, 99999); /*For mobile devices*/
    document.execCommand("copy");
    alert("Successfully copied TOTP secret token!");
  }
</script>
{% endblock %}

ما دستورالعمل هایی را در صفحه اضافه می کنیم تا کاربران آن را دنبال کنند. این دستورالعمل‌ها مراحل روشنی را برای کاربران فراهم می‌کند تا 2FA را فعال کنند: هدایت آن‌ها برای دانلود برنامه Google Authenticator از طریق پیوند، هدایت فرآیند راه‌اندازی در برنامه، و ترغیب کاربران به کلیک کردن روی پیوند پس از اسکن کد QR نمایش‌داده‌شده.

نمایش کد QR در فرآیند راه اندازی مرکزی است. الگو با استفاده از یک تصویر کد QR را جاسازی می کند <img> تگ با منبع تنظیم شده روی یک رشته کدگذاری شده با Base64 ({{ qr_image }}). این تصویر نشان دهنده کلید مخفی ضروری برای راه اندازی 2FA است.

ما همچنین کلید مخفی را در حالت فقط خواندنی نشان می‌دهیم و به کاربران امکان می‌دهد کلید را بدون تغییر آن مشاهده کنند. ما یک دکمه کپی اضافه کرده ایم تا کپی کردن کلید را برای کاربران آسانتر کنیم.

علاوه بر این، ما پیوندی به صفحه تأیید 2FA اضافه کرده‌ایم که کاربران را راهنمایی می‌کند تا پس از اسکن کد QR، مراحل راه‌اندازی را ادامه دهند. ما این قابلیت را در بخش بعدی پیاده سازی خواهیم کرد.

صفحه شما در حال حاضر چگونه به نظر می رسد:

اسکرین شات-2023-11-26-010925
صفحه راه اندازی 2FA

چگونه یک صفحه تأیید 2FA اضافه کنیم

در این بخش، تأیید 2FA را پیاده سازی می کنیم. برای شروع، ما به یک فرم OTP نیاز داریم که در آن کاربران می توانند OTP خود را وارد کنند. مطالب زیر را در قسمت اضافه کنید accounts/forms.py فایل:

class TwoFactorForm(FlaskForm):
    otp = StringField('Enter OTP', validators=[
                      InputRequired(), Length(min=6, max=6)])

این TwoFactorForm شامل فقط یک فیلد (otp) برای دریافت OTP از کاربران.

حالا بیایید از این فرم در قسمت استفاده کنیم verify-2fa.html فایل داخل templates/accounts پوشه:

{% extends "_base.html" %}

{% block content %}

<div class="row">
  <div class="col-md-4"></div>
  <div class="col-md-4">
    <main class="form-signin w-100 m-auto">
      <form role="form" method="post" action="">
        {{ form.csrf_token }}
        <h1 class="h3 mb-3 fw-normal text-center">Enter OTP</h1>

        <div class="form-floating">
          {{ form.otp(placeholder="OTP", class="form-control mb-2") }}
          {{ form.otp.label }}
            {% if form.otp.errors %}
              {% for error in form.otp.errors %}
              <div class="alert alert-danger" role="alert">
                {{ error }}
              </div>
              {% endfor %}
            {% endif %}
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">Verify</button>
      </form>
    </main>
  </div>
  <div class="col-md-4"></div>
</div>

{% endblock %}

قالب Jinja اساساً حاوی یک فرم با یک فیلد برای OTP و یک دکمه تأیید است.

اجازه دهید مسیری را ایجاد کنیم که ارسال این فرم را در داخل انجام می دهد accounts/views.py فایل:

@accounts_bp.route("/verify-2fa", methods=["GET", "POST"])
@login_required
def verify_two_factor_auth():
    form = TwoFactorForm(request.form)
    if form.validate_on_submit():
        if current_user.is_otp_valid(form.otp.data):
            if current_user.is_two_factor_authentication_enabled:
                flash("2FA verification successful. You are logged in!", "success")
                return redirect(url_for(HOME_URL))
            else:
                try:
                    current_user.is_two_factor_authentication_enabled = True
                    db.session.commit()
                    flash("2FA setup successful. You are logged in!", "success")
                    return redirect(url_for(HOME_URL))
                except Exception:
                    db.session.rollback()
                    flash("2FA setup failed. Please try again.", "danger")
                    return redirect(url_for(VERIFY_2FA_URL))
        else:
            flash("Invalid OTP. Please try again.", "danger")
            return redirect(url_for(VERIFY_2FA_URL))
    else:
        if not current_user.is_two_factor_authentication_enabled:
            flash(
                "You have not enabled 2-Factor Authentication. Please enable it first.", "info")
        return render_template("accounts/verify-2fa.html", form=form)

مسیر با مقداردهی اولیه یک فرم شروع می شود (TwoFactorForm) برای تأیید 2FA با استفاده از داده های به دست آمده از درخواست. پس از ارسال فرم، کد با چندین بررسی مشروط برای تأیید اعتبار OTP وارد شده توسط کاربر ادامه می یابد.

هنگامی که فرم با موفقیت ارسال و تأیید شد، کد صحت OTP را با استفاده از current_user.is_otp_valid(form.otp.data)، بررسی می کند که آیا OTP وارد شده برای کاربر فعلی معتبر است یا خیر. اگر OTP معتبر باشد، کد منطق زیر را اجرا می کند:

  • اگر OTP ارائه شده معتبر باشد و 2FA از قبل برای کاربر فعال باشد، یک پیام موفقیت آمیز فلش می شود که تأیید موفقیت آمیز 2FA را نشان می دهد و کاربر به URL خانه هدایت می شود.
  • اگر OTP معتبر باشد اما 2FA برای کاربر فعال نباشد، سعی می کند 2FA را برای آن کاربر فعال کند. پس از فعال سازی موفقیت آمیز، یک پیام موفقیت چشمک می زند و کاربر به URL خانه هدایت می شود.

علاوه بر این، اگر OTP وارد شده توسط کاربر نامعتبر باشد، کد یک پیغام خطا نشان می دهد که OTP نامعتبر است و کاربر را به URL تأیید 2FA هدایت می کند تا دوباره فرآیند تأیید را امتحان کند.

اسکرین شات-2023-11-26-011107
صفحه تأیید 2FA

با این کار اجرای تمامی ویژگی ها را تکمیل کردیم! 🎉

نحوه اجرای برنامه تکمیل شده برای اولین بار

اکنون که برنامه ما آماده است، می توانید ابتدا پایگاه داده را مهاجرت کرده و سپس برنامه را اجرا کنید.

برای مقداردهی اولیه پایگاه داده (ایجاد یک مخزن مهاجرت)، از دستور استفاده کنید:

flask db init

برای انتقال تغییرات پایگاه داده، از دستور زیر استفاده کنید:

flask db migrate

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

flask db upgrade

از آنجایی که این اولین بار است که برنامه خود را اجرا می کنیم، باید تمام دستورات بالا را اجرا کنید. بعداً، هر زمان که تغییراتی در پایگاه داده ایجاد کنید، فقط باید دو دستور آخر را اجرا کنید.

پس از آن، می توانید برنامه خود را با استفاده از دستور اجرا کنید:

python manage.py run

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

flask-two-factor-auth/
├── migrations/
├── src/
│   ├── accounts/
│   │   ├── __init__.py
│   │   ├── forms.py
│   │   ├── models.py
│   │   └── views.py
│   ├── core/
│   │   ├── __init__.py
│   │   └── views.py
│   ├── static/
│   │   └── styles.css
│   ├── templates/
│   │   ├── accounts/
│   │   │   ├── login.html
│   │   │   ├── register.html
│   │   │   ├── setup-2fa.html
│   │   │   └── verify-2fa.html
│   │   ├── core/
│   │   │   └── index.html
│   │   ├── _base.html
│   │   └── navigation.html
│   ├── __init__.py
│   └── utils.py
├── .env
├── config.py
└── manage.py

بسته شدن

در این آموزش یاد گرفتید که چگونه با استفاده از PyOTP احراز هویت دو مرحله ای را در برنامه Flask خود تنظیم کنید.

در اینجا پیوند به مخزن GitHub است. هر زمان که گیر کردید به راحتی آن را بررسی کنید.

در اینجا چند آموزش دیگر در مورد احراز هویت، تأیید ایمیل و OTP نوشته ام که ممکن است از آنها لذت ببرید:

  • نحوه تنظیم احراز هویت اولیه کاربر در یک برنامه Flask
  • نحوه تنظیم تأیید ایمیل در یک برنامه Flask
  • نحوه تولید OTP با استفاده از PyOTP در پایتون

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