Давайте признаем: пароли — это уязвимость. Если пароль легко запомнить, он, скорее всего, небезопасен. Это приводит к использованию таких приложений, как 1Password, для управления надежными паролями. Ситуация не идеальна, но работает. Работает до тех пор, пока крупные сайты не взламывают их базы данных. Велика вероятность, что кто-то уже видел один из ваших любимых паролей. Загляните на haveibeenpwned.com, если не верите. Если вы в безопасности — вам повезло. Пароли сами по себе — несовершенное решение для безопасности. Нужны ли они вообще?
Рикки Робинетт недавно написал пост в блоге, в котором подробно описал, как создать решение для безпарольной аутентификации с использованием PHP и Laravel. Его вдохновила реализация этой идеи в приложении Cover, чтобы избавиться от паролей. В этом посте мы создадим кроссплатформенный мобильный интерфейс на Xamarin, который будет использовать метод аутентификации из поста Рикки. Xamarin — это платформа для разработки мобильных приложений на языке C#. Мы будем использовать функцию Xamarin под названием Xamarin.Forms, которая позволяет писать пользовательский интерфейс в общем проекте. Этот общий проект будет генерировать UI для iOS, Android и Windows Phone из одного источника. После завершения ваши приложения для iOS, Android и Windows Phone смогут использовать эту технику, чтобы убрать пароли из процесса входа.
— Аккаунт Twilio с номером, поддерживающим SMS. Зарегистрируйтесь для бесплатной пробной версии здесь.
— Xamarin — я использую версию 5.8 из стабильной ветки. Вам понадобится как минимум лицензия Indie для использования Xamarin.Forms. Проект не запустится без хотя бы пробной версии. Зарегистрируйтесь для бесплатной 30-дневной пробной версии бизнес-лицензии.
— Xamarin Studio или Visual Studio (Visual Studio понадобится, если вы хотите собирать для Windows Phone).
— NuGet-пакет Microsoft HTTP Client Libraries.
— NuGet-пакет Json.Net.
— Порт бэкенда Рикки на ASP.NET MVC. Мы не будем разбирать код бэкенда, поэтому возьмите код из этого репозитория GitHub и запустите его с вашими учетными данными Twilio. Инструкции приведены в README.md.
Чтобы следовать за готовым кодом, я подготовил этот репозиторий GitHub для вас.
Этот пост посвящен созданию кроссплатформенного фронтенда, который выполняет входы в очень простой бэкенд, как описано в этом руководстве. PHP не является очевидным выбором для разработчика на C#, поэтому я портировал этот бэкенд для работы на ASP.NET MVC. Бэкенд имеет следующие конечные точки:
— /user/validate/ — POST-запросы на этот эндпоинт с номером телефона проверяют, связан ли аккаунт пользователя с этим номером телефона, и отправляют токен подтверждения.
— /user/auth/ — POST-запросы на этот эндпоинт с токеном проверяют, правильный ли токен для текущей сессии.
— /profile — GET-запросы на этот эндпоинт возвращают некоторые сверхсекретные данные. Доступ к этому эндпоинту возможен только после успешной аутентификации пользователя.
Поскольку мы сосредоточены на фронтенде, а Рикки уже отлично описал, как работает процесс, настоятельно рекомендуется прочитать его руководство. Предоставленная выше .NET-версия хорошо прокомментирована и соответствует эндпоинтам, созданным в этом посте.
Начнем с создания нового приложения Xamarin.Forms. Есть два варианта создания приложения Xamarin.Forms, и разница заключается в том, как они делят код. Если вам интересно узнать о различиях между двумя вариантами, в документации Xamarin есть отличное объяснение. Если вы хотите поддерживать Windows Phone, вам нужно использовать Visual Studio для создания приложения. Мы будем использовать опцию «Blank App (Xamarin.Forms Portable)» для создания нашего приложения и назовем его `PasswordlessLogin`.
Решение Xamarin.Forms состоит из одной переносимой библиотеки классов (PCL), которая будет содержать наш общий код UI для приложения, и специфичного для платформы проекта для iOS, Android и Windows Phone. Примечание: поддержка Windows Phone требует создания и сборки проекта в Visual Studio. Мы будем работать исключительно в проекте PCL, как показано на следующем скриншоте:
Если узел `Packages` под вашим проектом говорит, что есть обновления, как на скриншоте выше, это означает, что доступно обновление для Xamarin.Forms. Щелкните правой кнопкой мыши по решению и выберите `Update NuGet Packages`. Если вы используете Visual Studio, щелкните правой кнопкой мыши по решению и выберите `Manage NuGet Packages…`. Щелкните узел *Update* в списке и затем нажмите *Update All*.
Теперь добавим несколько NuGet-пакетов в проект. Пакеты, которые нам нужны для нашего приложения:
— Microsoft HTTP Client Libraries — добавляет поддержку объекта HttpClient, который мы будем использовать для отправки запросов к бэкенду из нашего приложения.
— Json.NET — для разбора JSON-результатов, возвращаемых бэкендом во время процесса аутентификации.
Чтобы установить пакет, щелкните правой кнопкой мыши по узлу `Packages` в проекте PasswordlessLogin и выберите `Add Packages…`. Найдите название пакета в строке поиска и добавьте его, нажав кнопку *Add Package*, как показано ниже:
Xamarin Studio:
Для Visual Studio щелкните правой кнопкой мыши по решению и выберите *Manage NuGet Packages for Solution…* и добавьте пакеты, как показано ниже:
С этими пакетами мы имеем все необходимое для начала создания нашего приложения.
Процесс безпарольной аутентификации начинается с отправки POST-запроса с номером телефона пользователя на `/user/validate`. Первое, что нам нужно реализовать, — это экран для ввода этого номера телефона. Экран в Xamarin.Forms называется `Page`, и есть два способа его создания. Один из способов — использовать код на C#. Откройте файл `PasswordlessLogin.cs` в основном проекте, и вы увидите следующий код в конструкторе класса App (если вы используете Visual Studio, этот файл может называться `App.cs`):
Так выглядит API на основе кода для создания UI. В нашем приложении мы будем использовать второй метод создания UI: XAML. XAML (eXtensible Application Markup Language) — это язык разметки, который позволяет создавать UI с использованием декларативного синтаксиса. Хотя поддержка инструментов минимальна на данный момент, в будущем XAML позволит Xamarin.Forms иметь отличные инструменты для создания UI, поэтому полезно потратить время на его изучение.
Щелкните правой кнопкой мыши по проекту PasswordlessLogin и выберите *Add->New File…* (в VS *Add->New Item…*) и добавьте *Forms ContentPage Xaml* (или *Forms Xaml Page* в VS) с именем `PhoneNumberPage`.
Это создаст два файла: `PhoneNumberPage.xaml` и `PhoneNumberPage.xaml.cs`. Первый будет содержать нашу разметку UI, а второй — код, который работает с объектами в нашем UI. Откройте `PhoneNumberPage.xaml` и замените его содержимое следующей разметкой:
Эта разметка указывает, что содержимое нашей страницы будет представлять собой StackLayout, который заполняет экран и центрирует свое содержимое по вертикали. Все, что добавляется в этот макет, будет располагаться вертикально друг над другом. Внутри этого StackLayout мы добавляем Label, Entry и Button. Мы предоставляем `x:Name` для Entry и Button, чтобы можно было ссылаться на них из файла кода (`PhoneNumberPage.xaml.cs`). Это все, что нам нужно сделать для компоновки UI для `PhoneNumberPage`. Однако, если мы запустим приложение на этом этапе, мы не увидим эту страницу, потому что мы не установили ее как главную страницу для нашего приложения Xamarin.Forms. Чтобы сделать это, откройте `PasswordlessLogin.cs` (или `App.cs` в шаблоне Visual Studio) и замените конструктор класса App следующим кодом:
Этот код создает `NavigationPage`, который позволит нам перемещаться вперед и назад по страницам в нашем приложении, и устанавливает корневую страницу для навигации как экземпляр `PhoneNumberPage`. На этом этапе стоит протестировать, правильно ли настроен наш UI, запустив приложение. Выберите специфичный для платформы проект по вашему выбору (PasswordlessLogin.iOS, PasswordlessLogin.Droid или PasswordlessLogin.WinPhone) и установите его как стартовый проект, щелкнув правой кнопкой мыши по узлу проекта и выбрав *Set As Startup Project*. Запустите приложение (на устройстве или в симуляторе), и вы должны увидеть следующий экран:
Вы сможете ввести номер телефона в поле Entry, но ничего не произойдет, если вы нажмете *NEXT*, потому что мы еще не написали код. Давайте сделаем это сейчас. Откройте `PhoneNumberPage.xaml.cs` и добавьте следующие операторы `using`:
Затем мы добавим поле для объекта `HttpClient`:
Добавьте строку в конструктор, чтобы подключить событие `Clicked` для нашего `NextButton`:
Наконец, добавьте обработчик событий для кнопки:
Обработчик событий создает экземпляр `HttpClient`. Затем он создает `KeyValuePair`, содержащий номер телефона, введенный в поле `PhoneNumberEntry`, чтобы передать его как данные формы закодированные в бэкенд. Затем он делает POST-запрос к эндпоинту `/user/validate` и проверяет результат на наличие значения `Success`, равного `true`. Не забудьте обновить URL в вызове `PostAsync` на URL вашего бэкенда. Пока мы просто отображаем всплывающее окно при успехе или неудаче, так как мы еще не создали следующую страницу. Запустите приложение и протестируйте его с вашим номером телефона (обязательно используйте формат ‘+15555551234’). Если вы получили подтверждение и SMS с токеном отправлено на ваш телефон, вы готовы перейти к следующему шагу.
На предыдущем шаге нам пришел токен по SMS. Чтобы завершить процесс аутентификации, нужно позволить пользователю ввести этот токен на следующей странице приложения. Давайте создадим страницу ввода токена, добавив новый файл `Forms ContentPage Xaml` в приложение с именем `TokenPage`. Откройте `TokenPage.xaml` и замените его содержимое следующей разметкой:
Эта разметка должна быть вам знакома, так как она почти идентична нашему предыдущему экрану. Единственное реальное отличие заключается в том, что поле `Entry` принимает токен вместо номера телефона на этой странице. Давайте поработаем над отправкой этого токена в бэкенд для проверки. Откройте `TokenPage.xaml.cs` и добавьте следующие операторы `using`:
Затем добавьте поле для объекта `HttpClient` в класс `TokenPage`:
Замените конструктор следующим кодом:
На этот раз мы передаем экземпляр `HttpClient` на страницу вместо создания нового. Это связано с тем, что бэкенд использует куки, и мы хотим убедиться, что используем ту же сессию при отправке запроса с токеном. Теперь добавьте обработчик событий `NextButton_Clicked`:
Этот код почти идентичен предыдущей странице, за исключением того, что на этот раз мы передаем значение токена на эндпоинт `/user/auth` для проверки. Не забудьте обновить URL в вызове `PostAsync` на URL вашего бэкенда. Если `Success` равно `true`, мы отобразим оповещение об успехе. В реальном приложении вы, вероятно, захотите перенаправить пользователя в защищенную часть вашего приложения. Последнее, что нам нужно сделать, — это заменить `Alert` в `PhoneNumberPage` навигацией на `TokenPage` при успешном вводе номера телефона. Отредактируйте проверку ответа в обработчике `NextButton_Clicked` файла `PhoneNumberPage.xaml.cs`, чтобы она содержала следующее:
Выделенная строка создает новый `TokenPage`, передавая объект `HttpClient`, и помещает его в стек навигации приложения.
На этом этапе все приложение готово к тестированию. Запустите его на вашей любимой платформе и протестируйте. Введите свой номер телефона и нажмите *NEXT*. Затем на следующей странице введите полученный токен и нажмите *NEXT*. Если все пройдет хорошо, вы должны увидеть сообщение об успехе:
Кричите об этом с крыш, пароль мертв! Или, по крайней мере, теперь он может быть таким, раз мы создали решение для безпарольной аутентификации, которое работает на всех трех основных мобильных платформах. Что мы могли бы сделать для улучшения решения, созданного в этом посте:
— Создать страницу регистрации для создания новых пользователей.
— Предоставить резервный метод аутентификации на случай, если у пользователя нет доступа к SMS.
— Реализовать голосовое решение для предоставления токена в дополнение к SMS.
— Создать страницу профиля для пользователя, к которой он сможет получить доступ только после успешной аутентификации.
Мне очень интересно увидеть, какие вещи вы создаете с помощью Twilio. Не стесняйтесь обращаться ко мне в любое время по электронной почте brent@twilio.com или в Twitter @brentschooley.