Решения AWS

Как создать службу бессерверной аутентификации с помощью AWS CDK, Cognito и API Gateway

Серверная служба, использующая файлы cookie TypeScript, JWT и HttpOnly.

Я использовал это. Ты тоже.

Это повсеместно. Это везде. Это важно.

Вы знаете, о чем я говорю: аутентификация.

Аутентификация описывает акт подтверждения утверждения, такого как ваша личность, в компьютерной системе [1].

Или проще говоря — вы говорите системе, кто вы есть.

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

В следующих разделах вы создадите серверную службу без сервера, используя Amazon Cognito, API Gateway и AWS Lambda.

Используя AWS Cloud Development Kit (CDK), вы сможете предоставлять инфраструктуру как код (IaC), что позволяет очень легко запускать или останавливать серверную службу с помощью простого оператора командной строки.

Однако, прежде чем погрузиться с головой в детали реализации, давайте сделаем шаг назад и кратко поговорим о высокоуровневом дизайне.

Общий обзор

Прежде чем писать какой-либо код, всегда полезно сначала представить полную картину. Убедитесь, что мы точно знаем, чего мы пытаемся достичь.

Итак, наша общая цель — создать бессерверную серверную систему, которая будет выполнять аутентификацию за нас. Но что это на самом деле означает?

Давайте быстро пройдемся по потоку выше:

  1. Пользователь либо пытается создать новую учетную запись, либо войти, предоставив какую-либо форму учетных данных (например, имя пользователя и пароль).
  2. Пользователь получает ответ. В случае успешного входа это будет HttpOnly Cookie с JSON Web Token внутри.
  3. Оснащенный файлом cookie, пользователь пытается получить доступ к защищенному ресурсу через другой шлюз API. Авторизатор Lambda проанализирует файл cookie, включенный в заголовок запроса, и проверит JWT. Если проверка прошла успешно, авторизатор возвращает пользователю документ политики, открывающий доступ к защищенному ресурсу.

И это уже оно.

Теперь давайте начнем с запуска нашей любимой IDE и создания нового проекта.

Примечание. Все используемые службы доступны на уровне бесплатного пользования, поэтому никаких дополнительных затрат не требуется. Тем не менее, рекомендуется проверить свою учетную запись AWS и закрыть все неиспользуемые сервисы.

Внедрение службы бессерверной аутентификации

Прежде всего, убедитесь, что у вас установлен и загружен AWS CDK. Следующий код может помочь:

// Install AWS CDK
npm install -g aws-cdk
// Bootstrap AWS CDK
cdk bootstrap aws://ACCOUNT-NUMBER/REGION

Теперь, когда у нас есть нужные инструменты, мы можем начать наш проект, просто создав новую папку и инициализировав CDK через интерфейс командной строки.

// Create a new folder
mkdir aws-serverless-auth
cd aws-serverless-auth
// Init CDK
cdk init --language typescript

После завершения процесса установки мы наконец можем открыть наш редактор кода и приступить к работе.

Создание пула пользователей Amazon Cognito

Перво-наперво.

Чтобы создать надлежащую службу аутентификации, мы должны сначала создать некоторую форму пользовательской базы данных. Для этой цели мы используем Amazon Cognito, который, к счастью, предоставляет нам все необходимые функции.

Внутри папки lib создайте новый файл с именем user-pool.ts.

В приведенном выше коде мы экспортируем класс с именем CognitoUserPool.

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

Обратите внимание, что мы предоставляем два поля класса только для чтения для дальнейшего использования, а именно userPoolId и userPoolClientId.

Создание API авторизации

Теперь давайте перейдем к гайкам и болтам — API аутентификации.

Еще раз внутри папки lib создайте новый файл с именем auth-api.ts.

Давайте начнем красиво и легко, сначала создав RestAPI и убедившись, что наш конструктор класса получает правильные свойства.

Далее мы добавляем новый ресурс и прикрепляем к нему несколько лямбда-функций. Чтобы сделать нашу жизнь намного проще, мы будем использовать частный вспомогательный метод с именем addRoute().

Примечание. В следующем разделе мы создадим все необходимые функции Lambda.

Каждая функция Lambda соответствует отдельному маршруту и ​​определенному действию пользователя.

Используя вспомогательный метод, мы можем не только уменьшить дублирование кода, но и предоставить каждой функции обязательные переменные среды, а также правильные политики (например, разрешить доступ к пулу пользователей Cognito).

Реализация лямбда-функций аутентификации

Все идет нормально.

Мы уже создали пул пользователей Cognito и RestAPI, что позволило нам раскрыть нашу логику аутентификации внешнему миру.

Однако нам еще предстоит реализовать такую ​​логику.

Внутри корневой папки проекта мы создаем новый каталог с именем lambda. Мы открываем новый каталог и создаем внутри подпапку с именем auth.

Примечание. Чтобы установить определения типов для AWS Lambda, введите npm install @types/aws-lambda в своем терминале.

Зарегистрироваться

Начнем с функции регистрации и создадим новый файл signup.ts.

Как только мы убедились, что получили правильное тело события (учетные данные пользователя), мы просто вызываем метод signUp() для экземпляра CognitoIdentityServiceProvider и возвращаем ответ пользователю.

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

Подтвердить регистрацию

Ну, ты знаешь упражнение.

Создайте новый файл с именем confirm-signup.ts.

Здесь нет ничего необычного.

Мы просто получаем имя пользователя и код подтверждения, которые передаем методу confirmSignup() метода CognitoIdentityServiceProvider. В конце мы возвращаем соответствующий ответ пользователю.

Войти

Теперь, когда мы можем создать нового пользователя, мы можем начать работать над функцией входа. По-прежнему внутри папки auth создайте новый файл с именем signin.ts.

Внутри функции входа мы собираем имя пользователя и пароль, чтобы вызвать метод initiateAuth(). Если данные учетные данные верны, мы извлекаем IdToken из AuthenticationResult и устанавливаем файлы cookie Secure и HttpOnly в заголовке ответа с токеном в качестве полезной нагрузки.

выход

Функция выхода очень проста.

Мы просто создаем новый файл signout.ts и «удаляем» файл cookie, устанавливая его срок действия в прошлом.

Создайте защищенный API и Lamba Authorizer

Мы приближаемся к финишной черте.

Теперь, когда мы уже реализовали API аутентификации и все необходимые функции Lambda, мы можем начать работу над последними недостающими частями: защищенным RestAPI и авторизатором Lambda.

Создайте защищенный API

Начнем с якобы легкой части.

Внутри папки lib мы создаем новый файл с именем protected-api.ts.

В приведенном выше коде мы определяем простой RestAPI, две лямбда-функции и их интеграцию.

protectedFn возвращает просто сообщение, что позволяет нам имитировать некоторые защищенные ресурсы. Мы создаем новый файл внутри папки lambda с именем protected.ts.

Предоставляя RequestAuthorizer и назначая наш авторизатор Lambda в качестве обработчика, мы обеспечиваем защиту маршрута.

Реализация лямбда-авторизатора

Лямбда-авторизатор делает две вещи:

  1. Он анализирует файл cookie, указанный в заголовке запроса, и проверяет JWT.
  2. Он возвращает документ политики, запрещающий или разрешающий доступ к ресурсу.

Достаточно просто, не так ли?

Давайте продолжим и создадим новый файл authorizer.ts внутри папки lambda/auth.

Внутри авторизатора мы используем три вспомогательные функции: parseCookies(), verifyToken() и createPolicy().

Давайте рассмотрим их далее.

Но сначала создайте новый файл utils.ts внутри папки lambda, в котором будут размещены все эти три вспомогательные функции. Вот как это выглядит:

Наша первая вспомогательная функция делает то, что следует из названия — она анализирует файлы cookie внутри заголовка запроса. В основном перебирая объект headers.Cookie и создавая cookieMap с именем и значением файла cookie.

Как только мы получим файл cookie с нашим токеном, нам нужно его проверить.

Следующая вспомогательная функция, verifyToken, зависит от трех внешних библиотек, поэтому убедитесь, что npm install axios jsonwebtoken jwk-to-pem.

Мы получаем веб-ключ JSON для нашего пула пользователей Cognito, запрашивая предоставленный URL-адрес. Далее конвертируем ключ с помощью внешней библиотеки jwk-to-pem. После преобразования ключа мы можем проверить токен и вернуть результат.

На основе этого результата мы создаем документ политики, либо разрешающий, либо запрещающий доступ к защищенному ресурсу. Для этой цели мы создаем нашу последнюю вспомогательную функцию, createPolicy().

Собираем все вместе

Фух — это было много работы.

Теперь осталось сделать только одно. Мы должны собрать все это вместе в наш окончательный стек.

Внутри папки lib откройте файл с именем aws-serverless-auth-stack.ts.

Здесь мы просто создаем экземпляры всех других классов, созданных ранее. Наш пул пользователей Cognito и оба RestAPI. Обратите внимание, что мы передаем userPoolId и userPoolClientId в качестве свойств в оба API.

Мы собрали все части. Хорошая работа.

Теперь пришло время развернуть стек, набрав cdk deploy в вашем терминале.

Тестирование потока с помощью Postman

Как только наш стек будет полностью развернут, мы наконец сможем протестировать общий поток связи, используя Postman.

Однако, прежде чем мы сможем начать тестирование, нам нужно получить как URL-адрес нашей аутентификации, так и защищенный API. Поэтому перейдите в консоль AWS, перейдите к шлюзу API, выберите каждый API, выберите этапы и скопируйте URL-адрес.

Давайте начнем, создав нового пользователя и зарегистрировавшись.

Внутри Postman мы создаем новый запрос POST с URL-адресом API аутентификации, который мы скопировали ранее. Наше тело запроса JSON просто содержит имя пользователя, адрес электронной почты и пароль.

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

Затем мы должны подтвердить наш запрос на регистрацию, введя код подтверждения, который мы должны были получить по почте. Создайте новый запрос POST внутри Postman с именем пользователя и кодом в теле.

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

Теперь мы можем протестировать функцию входа.

Мы создаем еще один POST-запрос, указав имя пользователя и пароль внутри тела.

В ответе мы должны были получить файл cookie с веб-токеном JSON внутри. Мы можем убедиться в этом, проверив наши файлы cookie внутри Postman.

Мы успешно вошли в систему. Отлично.

Теперь давайте попробуем получить доступ к нашим защищенным ресурсам.

Создайте запрос GET внутри Postman и выберите защищенный маршрут. Также не забудьте включить файл cookie token=<replace-with-jwt> в свой запрос.

Как видно из тела ответа — сработало. Мы получили супер секрет.

И это, наконец, это.

Мы закончили нашу службу бессерверной аутентификации.

Примечание. После завершения тестирования мы можем отключить инфраструктуру, набрав cdk destroy в нашем терминале.

Заключение

В этой статье мы создали службу бессерверной аутентификации, используя Amazon Cognito и API Gateway. Мы также использовали CDK, создавая нашу инфраструктуру как код, что позволило нам легко развернуть и разобрать весь стек.

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

Тем не менее, еще есть место для некоторых улучшений. У нас есть возможность зарегистрироваться только с именем пользователя и паролем. Это может быть дополнительно улучшено за счет интеграции федеративных поставщиков удостоверений, таких как Google, LinkedIn и т. д.

Спасибо за прочтение.

Вы можете найти полный код на моем GitHub.

Рекомендации