пароль + код на email - вполне себе двухфакторная аутентификация.
Одноразовый пароль, отправляемый по email можете просто генерировать рандомно на стороне сервера и потом сравнивать его с полученным от пользователя.
Отличие от SMS - канал передачи одноразового пароля. Вы можете даже использовать пуш-уведомления на смартфоне для передачи одноразовых паролей.
Другой вариант - OTP генератор. То есть у вас должен быть генератор одноразовых паролей (аппаратный или программный), а на стороне сервера должна быть реализована логика сервера аутентификации по стандарту OATH. Есть готовые модули реализующие этот стандарт.
Другой, возможно, несколько устаревший способ: TAN-списки. Раньше их использовали некоторые банки. На обычном принтере печатается лист с одноразовыми паролями, которые потом сервер запрашивает при аутентификации или, как в случае с банками, при авторизации транзакции. По сути метод аналогичен отправке пароля по sms/email, но тут сервер уже должен хранить список сгенерированных паролей, а не только текущий пароль.
Еще один способ - печать матрицы с именованными столбцами и строками. Сервер аутентификации при входе будет запрашивать пароль по координатам матрицы, например, A4,B1,F1,C3
Полагаю, что вы можете использовать сертификат пользователя как второй фактор в добавок к паролю. Это вообще уже реализовано на веб-серверах и нужно просто немного настроить конфиг. В продакшене для такой задачи конечно лучше использовать смарт-карты и разворачивать PKI, но для курсовой можно обойтись и openssl.
Есть и другие, более экзотические методы двухфакторной аутентификации.