Представим, что перед вами стоит задача организовать аутентификацию пользователя (в мобильном приложении, в первую очередь) так, как это сделано в Telegram/Viber/WhatsApp. А именно реализовать в API возможность осуществить следующие шаги:
- Пользователь вводит свой номер телефона и ему на телефон приходит СМС с кодом.
- Пользователь вводит код из СМС и приложение его аутентифицирует и авторизует.
- Пользователь открывает приложение повторно, и он уже аутентифицирован и авторизован.
Я постараюсь кратко изложить выработанный подход к этому вопросу. Подразумевается, что у вас API, HTTPS и, вероятно, REST. Какой у вас там набор остальных технологий неважно. Если интересно — добро пожаловать под кат.
Изменения в API
В сущности требуется добавить два добавить три метода в ваше API:
- Запросить СМС с кодом на номер, в ответ — токен для последующих действий.
Действие соответствует CREATE в CRUD.
POST /api/sms_authentications/
Параметры на вход:
phone
Параметры на выход:
token
Если всё прошло, как ожидается, возвращаем код состояния 200.
Если же нет, то есть одно разумное исключение (помимо стандартной 500 ошибки при проблемах на сервере и т.п. — некорректно указан телефон. В этом случае:
HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: PHONE_NUMBER_INVALID.
- Подтвердить токен с помощью кода из СМС.
Действие соответствует UPDATE в CRUD.
PUT /api/sms_authentications/<token>/
Параметры на вход:
sms_code
Аналогично. Если всё ок — код 200.
Если же нет, то варианты исключений:
- Некорректный токен: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOKEN_INVALID.
- Некорректный код: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: SMS_CODE_INVALID.
- Форсированная отправка кода повторно.
PUT /api/sms_authentications/<token>/resend
Аналогично. Если всё ок — код 200.
Если же нет, то варианты исключений:
- Некорректный токен: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOKEN_INVALID.
- Слишком частая отправка (скажем, прошлая отправка была не позднее чем 60 секунд назад): HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOO_OFTEN.
Помимо этого, каждый метод API, который требует аутентифицированного пользователя должен получать на вход дополнительный параметр token
, который связан с пользователем.
Литература:
- Образец для подражания — API Telegram: https://core.telegram.org/methods
- Дискуссия на SOF: http://stackoverflow.com/questions/12401255/sms-registration-like-in-the-mobile-app-whatsapp
Изменения в коде сервера
Вам потребуется хранить специальный ключ для проверки СМС-кодов. Существует алгоритм TOTP, который позволяет, цитирую Википедию:
OATH-алгоритм создания одноразовых паролей для защищенной аутентификации, являющийся улучшением HOTP (HMAC-Based One-Time Password Algorithm). Является алгоритмом односторонней аутентификации — сервер удостоверяется в подлинности клиента. Главное отличие TOTP от HOTP это генерация пароля на основе времени, то есть время является параметром[1]. При этом обычно используется не точное указание времени, а текущий интервал с установленными заранее границами (например, 30 секунд).
Грубо говоря, алгоритм позволяет создать одноразовый пароль, отправить его в СМС, и проверить, что присланный пароль верен. Причём сгенерированный пароль будет работать заданное количество времени. При всём при этом не надо хранить эти бесконечные одноразовые пароли и время, когда они будут просрочены, всё это уже заложено в алгоритм и вы храните только ключ.
Пример кода на руби, чтобы было понятно о чём речь:
totp = ROTP::TOTP.new("base32secret3232")
totp.now # => "492039"
# OTP verified for current time
totp.verify("492039") # => true
sleep 30
totp.verify("492039") # => false
Существует масса реализацией этого алгоритма для многих языков: для Ruby и Rails, для Python, для PHP и т.д..
Итого, в модели (или в таблице БД, если угодно) надо хранить:
- Телефон: phone (советую использовать библиотеки для унификации телефонного номера, вроде этой для Rails),
- Ключ для TOTP: otp_secret_key (читаете подробное README для выбранной библиотеки TOTP),
- Токен: token (создаете при первом запросе к API чем-нибудь типа SecureRandom),
- Ссылку на пользователя: user_id (если у вас есть отдельная таблица/модель, где хранятся данные пользователя).
Особенности реализации мобильного приложения
В случае Android полученный токен можно хранить в SharedPreferences, а для iOS в KeyChain. См. обсуждение на SoF.
Заключение
Вышеописанный подход позволит вам в рамках вашего стека технологий реализовать указанную задачу. Если вас есть соображения по этому подходу или альтернативные подходы, то прошу поделиться в комментариях. Аналогичная просьба, если у вас есть примеры документации к безопасным системам хранения в других мобильных ОС.
Автор: mpetrunin