Организация аутентификации по СМС по примеру Telegram-Viber-WhatsApp

в 16:49, , рубрики: api, php, python, rails, ruby, sms, telegram, Time-based One Time Password, totp, WhatsApp, аутентификация, мобильный клиент, разработка мобильных приложений, смс-авторизация, смс-аутентификация

Представим, что перед вами стоит задача организовать аутентификацию пользователя (в мобильном приложении, в первую очередь) так, как это сделано в 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.

Если же нет, то варианты исключений:

  1. Некорректный токен: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOKEN_INVALID.
  2. Некорректный код: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: SMS_CODE_INVALID.

  • Форсированная отправка кода повторно.

    PUT /api/sms_authentications/<token>/resend

Аналогично. Если всё ок — код 200.

Если же нет, то варианты исключений:

  1. Некорректный токен: HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOKEN_INVALID.
  2. Слишком частая отправка (скажем, прошлая отправка была не позднее чем 60 секунд назад): HTTP код состояния: 400 (BAD_REQUEST), в теле ответа: TOO_OFTEN.

Помимо этого, каждый метод API, который требует аутентифицированного пользователя должен получать на вход дополнительный параметр token, который связан с пользователем.

Литература:

  1. Образец для подражания — API Telegram: https://core.telegram.org/methods
  2. Дискуссия на 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 и т.д..

Итого, в модели (или в таблице БД, если угодно) надо хранить:

  1. Телефон: phone (советую использовать библиотеки для унификации телефонного номера, вроде этой для Rails),
  2. Ключ для TOTP: otp_secret_key (читаете подробное README для выбранной библиотеки TOTP),
  3. Токен: token (создаете при первом запросе к API чем-нибудь типа SecureRandom),
  4. Ссылку на пользователя: user_id (если у вас есть отдельная таблица/модель, где хранятся данные пользователя).

Особенности реализации мобильного приложения

В случае Android полученный токен можно хранить в SharedPreferences, а для iOS в KeyChain. См. обсуждение на SoF.

Заключение

Вышеописанный подход позволит вам в рамках вашего стека технологий реализовать указанную задачу. Если вас есть соображения по этому подходу или альтернативные подходы, то прошу поделиться в комментариях. Аналогичная просьба, если у вас есть примеры документации к безопасным системам хранения в других мобильных ОС.

Автор: mpetrunin

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js