Автентифікація для своїх API

Розробляючи API для спілкування програми ZennoPoster з базою MySQL я використовую PHP. І для найпростіших публічних API, коли мені потрібно тільки читати дані з таблиць MySQL мені не потрібна реєстрація. Хоча в деяких випадках я все таки використовую API з ідентифікацією, щоб кожний потік чи кожний шаблон ZennoPoster працював з своїми даними. Хоча інколи потрібно ускладнити роботу.

Ідентифікація, це процес представлення користувача для сервіса. В моєму випадку ZennoPoster звертаючись до моїх API сервісів може відправляти наприклад UserAgent: ZP. І так як зазвичай браузер Chrome має свій унікальний чи не дуже UserAgent, то я можу чітко ідентифікувати, що запити прийшли від моєї копії програми ZennoPoster.

Коли мені потрібно ідентифікувати роботу конкретного проєкта ZennoPoster, я можу також добавити відповідний заголовок, який на стороні API я також можу обробляти, сервіс буде вважати, що це саме цей проєкт відправив запит.

Точно так, я можу при старті потока виконання проєкта ZennoPoster згенерувати унікальний GUID, який також відправляти в заголовках, щоб ідентифікувати саме окремий потік виконання роботи.

І, в результаті в мене вже назбиралось декілька заголовків з даними, які мені потрібно відправляти завжди звертаючись до своїх сервісів API.

Так от, в якийсь момент я сижу за комп’ютером, записую відео, на якому демонструю робочий стіл, показую відправку запитів до свого сервісу API. А на іншій стороні інтернету, десь під пальмою сидить користувач, якого зацікавило це відео. І переглядаючи його він побачив мої ідентифікатори, які відправлялись разом з запитами. Звернув увагу, що один заголовок взагалі не змінюється, тому що він одинаковий для цілої програми, інший змінювався в залежності від проєкту, який я відкривав. А останній змінювався тільки коли я починав виконувати проєкт спочатку.

Методом копіювання цих ідентифікаторів користувач який їх побачив може звертатись до мого сервісу API, і одержувати необхідну інформацію. Власне інформація не секретна, нічого страшного. Але на стороні сервісу записуються ідентифікатори які звертались до сервісу. І через якийсь час, зайшовши в базу даних і проаналізувавши, я зміг би побачити, що мої шаблони чомусь відправляють набагато більше запитів, ніж зазвичайно. Почав би пробувати виправляти помилки, і напевне ніколи не дізнався би, що причиною цього стала звичайна демонстрація робочого стола на відео.

Тому, щоб уникнути такої ситуації, прийшлось вигадувати спосіб автентифікації (детальніше в Вікіпедії https://w.wiki/82Yn ). Автентифікація – це підтвердження того, що користувач, який звертається до API, є саме тим, за кого себе видає (в моєму випадку що це ZennoPoster, саме мій проєкт). Сама звичайна автентифікація відбувається коли користувач дає наприклад email і пароль, сервіс проводить ідентифікацію, що такий email вже існує в базі даних, після чого перевіряє що цьому email відповідає саме цей пароль. Продовження історії – це видача права користуватись ресурсом, повернення наприклад session_id в cookie. Після чого браузер відправляє з кожним запитом ці cookie, а в обмін сервіс повертає необхідні дані.

Але, в моєму випадку такий спосіб автентифікації не підходить зовсім, тому що кожний запит який робить ZennoPoster мені приходиться демонструвати у своєму відео. Тому, мені потрібно щоб заголовок, який буде відправлятись на сервіс змінювався постійно. Тоді якщо хтось навіть побачить і попробує повторити такий самий запит – сервіс повинен зрозуміти що цей запит був відправлений не від мого ZennoPoster.

Як реалізувати такий функціонал? Я вирішив зробити так – при першому зверненні з свого шаблона, у відповідь сервіс повинен видати мені новий згенерований секрет 2FA. А всі інші заголовки, які я описав вище звичайно можна використовувати дальше для ідентифікації. І цей секрет я можу просто прописати в коді, чи наприклад зберегти поряд з проєктом в текстовому файлі.

Тоді, при кожному запиті до API, мої проєкти будуть генерувати завжди інші коди 2FA і добавляти їх в заголовки до запитів. На стороні API при одержанні запиту, будуть прочитані заголовки, після чого з бази даних буде одержаний збережений секрет для ідентифікатора проєкта, який був збережений при першому зверненні. Потім буде згенерований 2FA код, і він буде порівняний з тим, який був знайдений у запиті.

Якщо коди співпадуть, тоді у відповідь на запит буде відправлена необхідна інформація. А у випадку, якщо коди не співпадуть – тоді буде повернута помилка авторизації (тому що аутентифікація не була пройдена корректно).

З цього часу, я можу записувати відео, демонструвати всі запити, які відправляються програмою ZennoPoster до моїх API. А от уважні глядачі, які попробують повторити запити з моїми заголовками будуть отримувати помилку. Тобто не зможуть одержати доступ до функцій сервісу, які були закритими цією автентифікацією.

Що тут потрібно напевне добавити, що використовувати такий підхід можна, я його використовував неодноразово. Але, потрібно розуміти, що якщо користувачу відомий ідентифікатор проєкту (він його бачив), і відома логіка, по якій була спроба захистити API за допомогою такої автентифікації, то він може пробувати просто відправляти цей ідентифікатор і випадково згенероване значення. І може відправляти таких запитів дуже багато за короткий проміжок часу. Що власне приведе до того, що він зможе одержати доступ до необхідних даних (обмеження по IP не спрацює, тому що люди з ZennoPoster будуть використовувати проксі, а обмеження на кількість запитів не допоможе, тому що мої проєкти повинні працювати в багато потоків одночасно). Тому, використовуючи такий підхід до автентифікації потрібно на стороні сервісу вказати скільки саме неуспішних спроб можна робити до того часу, поки сервіс не почне відкидати всі запити для цього ідентифікатора. Наприклад всі неуспішні спроби добавляємо в таблицю MySQL, і перевіряємо, якщо вже було більше 10 неуспішних спроб аутентифікації – не пропускаємо дальше цей ідентифікатор, відправляємо повідомлення власнику сервіса в телеграм чи на пошту (але і тут треба розуміти що відправляти повідомлення треба наприклад 1 раз в 10 хвилин, а не при кожній не успішній спробі, інакше може виявитись що ми дуже швидко перевищимо ліміт телеграма чи наші листи на email будуть помічатись як спам).

Значить ще раз – для одного ідентифікатора при першому зверненні видаємо секрет 2FA.
А потім проводимо автентифікацію, тобто перевірку що власник цього ідентифікатора дійсно знає секрет 2FA (а значить може згенерувати одноразовий код, який на своїй стороні зможе згенерувати і сам сервіс, при чому сам секрет 2FA по мережі повторно не буде переданий ).

Один з варіантів, над якими я думав також – користувач відправляє унікальний ідентифікатор.
Сервіс у відповідь віддає унікальний одноразовий код (який не дійсний після першого використання).
Можна видати його наприклад у вигляді QR кода.

Користувач сканує QR код і попадає до телеграм бота, який в параметрі старт приймає цей одноразовий код.
Телеграм відправляє запит до телеграм бота, і бот реєструє користувача телеграм як власника ідентифікатора.
У відповідь повертає секрет 2FA, який користувач вводить у своєму проєкті ZennoPoster, і при кожному запиті відпрвляє одноразові коди.

Такий підхід позволяє не передавати секрет 2FA у відкритому вигляді, а за сторони користувача – то він доступ до нього може одержати з телефона, який може бути не підключеним до тієї з мережі що компьютер на якому стоїть ZennoPoster. А у випадку виявлення не санкційного доступу – оповіщати його через телеграм чи email про проблему.

Власне я попробував описати спосіб, який дозволяє відправляти не одинаковий session_id, як це відбувається при звичайних php сессіях, а кожного разу інший. При чому це можна робити беспечно у відкритому вигляді (знаючи що будь-хто будь-коли зможе побачити цей одноразовий код, але доступ по ньому до ресурсів одержати не зможе). Щось подібне я використовую у своїх проєктах, коли є така необхідність спілкування MySQL і ZennoPoster використовуючи свої сервіси API.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *