Автентифікація користувача в BaaS Parse.com через соціальні мережі

Я буду щоранку розгортати світ, як гумову стрічку на м'ячі для гольфу, а ввечері загортати назад. Якщо дуже попросиш — покажу, як це робиться.
Р. Бредбері

Введення

У статті описано Backend-as-a-Service підхід до зберігання та обробки даних. Розкриті переваги і недоліки представника такого підходу — сервісу parse.com. Коротко представлений сервіс аутентифікації користувачів через соц. мережі uLogin. Основне призначення — показати, як ці два сервіси можуть взаємодіяти, щоб проект не вимагав реєстрації користувачів по логіну і паролю, але в той же час збереглася можливість авторизації користувачів до дій над об'єктами.

Про BaaS і parse.com

Parse.com — один з найпопулярніших провайдерів backend-as-a-service (BaaS). BaaS підхід дозволяє не піднімати свій сервер для зберігання та обробки даних програми. Це використовується в мобільних розробках і в звичайному вебі. Parse.com має свої SDK під декілька платформ, в тому числі серверних. Але я розповім про javascript.

Можливість працювати з базою даних через javascript, не піднімаючи свій сервер, відкриває відмінні можливості, наприклад, для Single page application (SPA), яке можна хостити на Github Pages, Bitbucket і багатьох інших безкоштовних. Перше питання, яке у мене виникло, коли я почув про роботу з БД із клієнтського коду — це розмежування прав доступу, так як ключі загальновідомі. Вивчивши документацію parse.com, я з'ясував, що для цього використовується авторизація користувачів. Кожен користувач має свій логін і пароль. SDK має методи реєстрації нового користувача по логіну і паролю, аутентифікації за цими ж даними. Можна додати email, при цьому сам parse.com уміє відправляти настроювані листи для верифікації email.

Приклад коду з реєстрацією
var user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "email@example.com");

// other fields can be set with just like Parse.Object
user.set("phone", "415-392-0202");

user.signUp null, {
success: function(user) {
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
});



Розмежування прав доступу відбувається за ACL, які можна призначати створюваних об'єктів. Наприклад, встановити для об'єкта доступ на публічне читання, але залишивши редагування тільки авторизованому користувачеві. Додатково можна встановлювати обмеження на методи по роботі з таблицею. Наприклад, можна дозволити всім запис в таблицю, але заборонити читання всім, крім певної групи (у випадках логування, наприклад). Крім цього, адміністратору надається майстер-ключ, з допомогою якого можна з клієнтського додатка отримати повний доступ. Але в SPA таке неприпустимо, так як ключ не сховати. Зрозуміло, є адмінка, де можна редагувати схеми, проводити CRUD-операції над усіма даними.
Адмінка:
image

Постановка проблеми

Аутентифікація користувача тільки за логіном і паролем зараз може виглядати грубо. Тому parse.com має в своєму SDK клас Parse.FacebookUtils, за допомогою якого можна свзязать акаунти користувача з соц. мережею. Але це єдина соціальна мережа, яку можна використовувати через SDK parse.com в javascript. Є Twitter, але javascript. Що робити, якщо дуже хочеться аутентифікувати користувачів через інші соц. мережі?

Cloud Code

Parse.com, на щастя, надає не тільки сховище даних, але й можливість виконувати певний код на їх сервері. Фічу вони назвали Cloud Code. Код створюваних функцій недоступний для користувачів, а значить це можна використовувати при побудові підписуються секретним ключем запитах. Але це не особливо-то і стала в нагоді.

Я почав вибирати сервіси, які мають готові віджети авторизації користувачів на сайтах, і зупинився на uLogin. Пару слів про вибір. Коли мені попадався на очі Логинза, але я залишився ним не задоволений, так як там мене просили вводити логін і пароль навіть для входу через Твіттер. Можливо, були якісь інші, соц. додатки яких просили доступ на написання твітів (omg, бісить). Так от, uLogin має хороший набір соц. мереж, готовий віджет, повернення accessToken callback-фунції для javascript, а також дозволяє створювати свої соц. програми для аутентифікації користувачів.

Так як parse.com просить реєструвати користувачів по логіну і паролю, то я прийшов до висновку, що цю пару можна отримати, використовуючи дані, які повертаються uLogin. Для цього необхідно створити файл clound/main.js у вашому додатку, в якому потрібно визначити функцію. Назвемо її «getCredentials».
Parse.Cloud.define("getCredentials", function(request, response) {
var token = request.params.token;
var userLogin, userPassword;

response.success({'username': userLogin, 'password': userPassword});
});


Сервіс uLogin дозволяє налаштувати список полів, які він буде повертати. Але серед обов'язкових є:
network — ідентифікатор соцмережі користувача,
profile — адреса профілю користувача (посилання на його сторінку в соцмережі, якщо вдасться його отримати),
uid — унікальний ідентифікатор користувача в рамках соцмережі,
identity — глобально унікальний ідентифікатор кожного користувача.

identity — це, як правило, теж url, схожий на profile, тому я вирішив склеювати логін користувача (тут username) network uid.
Для паролю потрібно щось секретне, тому я вирішив в якості пароля використовувати хеш від identity, token, secret.
identity для унікальності, token для секретності, т. до. кожен вхід в uLogin буде створювати новий маркер, відомий тільки користувачу і самому uLogin, secret — сіль, збережена в налаштуваннях програми.
Цей підхід поганий тим, що з кожним новому входом через uLogin, буде новий token, а значить і новий пароль. Тому потрібно кожен раз зберігати новий пароль користувача. Однак, parse.com після входу користувача досить довго зберігає сесію, тому аутентифицироваться кожен раз не доведеться. Готовий Cloud Code.

Рішення досить підозріле за якістю, але в деяких випадках цілком працездатно. Наприклад, якщо відмовитися від входу користувачів по їх логінів і паролів. Але навіть у цьому випадку метод можна доопрацювати і перестати оновлювати пароль користувача після отримання сертифіката. Як ще один варіант, це використовувати в якості тимчасового пароля випадкову рядок замість хешу (усвідомлення цього прийшло трохи пізніше).

Зрозуміло, тимчасовий пароль видається після підтвердження сертифіката, шляхом отримання даних з uLogin. Після цього ми визначаємо, чи є у нас користувач з таким логіном. Якщо ні, то реєструємо. Записуємо йому дані з соц. мережі (псевдонім, зображення) в сховище, повертаємо пару логін і пароль.
Взаємодія з uLogin c parse.com відбувається по https, всі дані доступні тільки самому користувачеві.

Аутентифікація клієнта
Потрібно зовсім небагато коду, щоб отримати аутентифицированного в parse.com користувача.
Підключити віджет uLogin
<script src="//ulogin.ua/js/ulogin.js"></script>
<div id="uLogin" data-ulogin="display=small;fields=first_name,last_name;providers=vkontakte,odnoklassniki,mailru,facebook;hidden=other;redirect_uri=&callback=authCallback"></div>

Додати код callback-функції.
var user;
window.authCallback = function (token) {
Parse.Cloud.run('getCredentials', {token: token}, {
success: function (data) {
Parse.User.logIn(data.username, data.password, {
success: function () {
user = Parse.User.current();
},
error: function (user, error) {
//handle it
}
});


}
});
};

Тут відбувається виклик написаної нами раніше серверної функції "getCredentials" в Cloud Code.
При наступному заході користувача за допомогою var user = Parse.User.current(); можна визначити, аутентифікований користувач чи ні. Якщо так, то віджет uLogin слід приховати.

Плюси і мінуси односторінкового додатки з використанням parse.com

Плюси
  • Немає свого сервера, який потрібно підтримувати
  • Безкоштовно 20GB для файлів, 20GB для даних, 2TB трафіку, 30 запитів в секунду, немає обмеження за кількістю користувачів
  • Можливість виконувати js-код на сервері
  • Робота з файлами (створення, отримання вмісту, отримання url)
  • Непогана готова аналітика з графіками, подіями та інше.


Мінуси:
  • Затримка отримання даних
  • Немає автоматичного резервного копіювання (є кнопка для створення дампа)
  • Немає автоматичного очищення непотрібних файлів (є кнопка для цього)
  • Необхідність проектування з метою зменшення кількості запитів до бэкэнду
  • Залежність від двох зовнішніх сервісів: parse.com і uLogin (другу залежність можна легко замінити на свій сервер)


Зауваження:
Можливе написання інших клієнтів до тих же загальнодоступним даними. Якщо клієнтська сторона дозволяє автентифікувати користувача через uLogin, то немає ніяких перешкод, щоб керувати даними користувача. Недолік в тому, що ліміт на кількість запитів загальний.

Демо-проект

Подивившись на imhonet, bookmix, livelib у мене з'явилася думка зробити свій сервіс для зберігання прочитаних книг. Головна мета якого — список книг, без зайвих довесов начебто оцінок, покупок, обкладинок. При виборі подібних сервісів, цікавить надійність зберігання даних, щоб не вийшло так, що проект закриється через рік-другий, а дані будуть втрачені. Тому при розробці свого сервісу я вирішив використовувати зовнішні безкоштовні сховища даних, тобто BaaS. Це дозволить не оплачувати хостинг, не турбується про проблеми оновлення. Статику планував тримати на Github Pages. Але раз сервіс став залежати від parse.com, то і хостингом вирішив користуватися теж його. Для SPA вирішив використовувати AngularJS, так як був з ним знайомий і знав, що мені від нього потрібно. Знавці поділу сутностей помітять дивні залежності між ними, але з кожним проектом на AngularJS я дізнаюся щось нове про нього. Тепер знаю, як зробити краще. Переписувати тільки з академічних цілей порахував зайвим.

Деякі можуть сказати, що реєстрація користувачів тільки через соц. мережі може спричинити серйозні труднощі з відновлення доступу до проекту у разі втрати доступу до соц. мережі. Цю проблему можна було б вирішити, налаштувавши uLogin так, щоб він запитував email користувача (і сам верифицировал його), а потім відновлювати доступ по email. Але в демо-проект такого немає, так як список прочитаних книг загальнодоступний, а значить, в разі серйозних проблем, його можна просто продублювати на інший обліковий запис.

Вихідний код
Демо

P. S.: Демо — це мій продакшн, вимкнений не буде. Від хабраэффекта можливі помилки 155 (RequestLimitExceeded) при завантаженні даних — це нормально, просто спробуйте пізніше.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.