Что такое jwt?
JSON Web Token (JWT) — это закодированный в строковой форме JSON-объект. Токены можно воспринимать как замену куки-файлов, имеющую несколько преимуществ перед ними.
Токен состоит из трёх частей. Это — заголовок (header), полезная нагрузка (payload) и подпись (signature). На следующем рисунке показан его внешний вид.
JWT
Данные токена могут быть декодированы на стороне клиента без использования секретного ключа или подписи.
Это может быть полезным для передачи, например метаданных, закодированных внутри токена. Подобные метаданные, могут описывать роль пользователя, его профиль, время действия токена, и так далее. Они могут быть предназначены для использования во фронтенд-приложениях.
Вот как может выглядеть декодированный токен.
Декодированный токен
Почему токены защищены от злоумышленников?
Почитав об использовании JWT, вы можете задаться следующим вопросом: «Если данные JWT могут быть декодированы на стороне клиента — можно ли так обработать токен, чтобы изменить идентификатор пользователя или другие данные?».
Декодирование токена — операция очень простая. Однако нельзя «переделать» этот токен, не имея той подписи, тех секретных данных, которые были использованы при подписывании JWT на сервере.
Именно поэтому так важна защита этих секретных данных.
Наш сервер проверяет подпись в промежуточном ПО isAuth. За проверку отвечает библиотека express-jwt.
Теперь, после того, как мы разобрались с тем, как работает технология JWT, поговорим о некоторых интересных дополнительных возможностях, которые она нам даёт.
Authentication integration completed
That’s it! In this tutorial, you learned how Passport.js works, how to configure it, and how to integrate it with Node.js and Auth0 to add authentication to web applications. You also learned about security and identity best practices and how an identity platform such as Auth0 lets you delegate to a team of experts the giant responsibility of keeping logins secure.
All that is left is for you to continue building this application as you may like. Feel free to dive deeper into the Auth0 Documentation to learn more about how Auth0 helps you save time on implementing and managing identity. Use the comments below this blog post to ask questions or join the Auth0 Community to connect with other developers like yourself.
Thank you for your time!
I ran into an issue
Configure express-session
🛠️️ Next, open index.js and add the following under the Required External Modules section:
const express =require("express");const path =require("path");const expressSession =require("express-session");const passport =require("passport");const Auth0Strategy =require("passport-auth0");require("dotenv").config();
You are adding imports for express-session, passport, and passport-auth0, which you’ll configure in the next sections.
🛠️️ Between the App Variables and App Configuration sections, create two new sections, Session Configuration and Passport Configuration:
const app =express();const port = process.env.PORT||"8000";
Order matters in Express. Please ensure that you add the sections in the right order.
🛠️️ Under Session Configuration, configure expressSession as follows:
const session ={
secret: process.env.SESSION_SECRET,
cookie:{},
resave:false,
saveUninitialized:false};if(app.get("env")==="production"){
session.cookie.secure =true;}
expressSession takes a configuration object, session, that defines what options to enable in a session. Here, you are configuring the following options:
Configure passport with the application settings
In this section, you’ll focus on wiring up Passport.js with your Express app.
🛠️️ With Auth0Strategy already imported, proceed to define this strategy under the Passport Configuration section in index.js:
const strategy =newAuth0Strategy({
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL},function(accessToken, refreshToken, extraParams, profile, done){returndone(null, profile);});
Here, the Auth0Strategy method takes your Auth0 credentials and initializes the strategy. It’s essential to understand what the Auth0Strategy is doing for you:
Create custom middleware with express
🛠️️ In index.js, update the App Configuration section to enhance the response object, res, with data from the authentication server. Do this right above the mounting of the authentication router:
Log into a node.js express app
🛠️️ Head to the browser tab where your application is running and click the login button to test that it is communicating correctly with Auth0 and that you can get authenticated.
🛠️️ If you’ve set up everything correctly, the application redirects you to the Universal Login page.
Set up real-world authentication for node.js
This tutorial’s core objective is to teach you how to set up real-world authentication in a Node.js Express app. For that reason, you’ll start by setting up Auth0 to interact with a real authentication server throughout the tutorial. Otherwise, Passport.js gives you a ton of error messages in the terminal, and the app won’t run.
Auth0 is a global leader in Identity-as-a-Service (IDaaS). Its extensible platform seamlessly authenticates and secures more than 2.5 billion logins per month, making it loved by developers and trusted by global enterprises.
The best part of the Auth0 platform is how streamlined it is to get started by following these three easy steps.
Step 1: sign up and create an auth0 application
🛠️️ If you are new to Auth0, sign up for a free Auth0 account here. A free account offers you:
🛠️️ During the sign-up process, you’ll create something called a Tenant, representing the product or service to which you are adding authentication — more on this in a moment.
🛠️️ Once you are signed in, you are welcomed into the Auth0 Dashboard. In the left sidebar menu, click on “Applications”.
Step 3: add auth0 configuration variables to node.js
🛠️️ Under the project directory, create a hidden file called .env to store configuration variables and secrets that your app needs.
touch .env
Make sure to add this file to .gitignore so that it isn’t committed to version control.
🛠️️ Add the following to .env:
AUTH0_CLIENT_ID=AUTH0_DOMAIN=AUTH0_CLIENT_SECRET=
🛠️️ Head back to your Auth0 application Settings tab and populate each property of the .env hidden file with its corresponding Auth0 application value:
Use middleware for authentication
As explained in the “Using middleware” section of the Express docs, an Express application is essentially a series of middleware function calls that execute during the request-response cycle. Each function can modify the request and response objects as needed and then either pass control to the next middleware function or end the request-response cycle.
What you will build
You’ll secure the login portal for a restaurant named WHATABYTE using Passport.js with Auth0:
We tested this tutorial using Node.js v12.16.0 and npm v6.13.
Верификация
Подключим и настроим стратегию авторизации.
Вход пользователей в систему
Вот как выглядит схема действий, выполняемых в том случае, когда пользователь пытается войти в систему.
Вход пользователя в систему
Вот что происходит при входе пользователя в систему:
- Клиент отправляет серверу комбинацию, состоящую из публичного идентификатора и приватного ключа пользователя. Обычно это — адрес электронной почты и пароль.
- Сервер ищет пользователя в базе данных по адресу электронной почты.
- Если пользователь существует в базе данных — сервер хэширует отправленный ему пароль и сравнивает то, что получилось, с хэшем пароля, сохранённым в базе данных.
- Если проверка оказывается успешной — сервер генерирует так называемый токен или маркер аутентификации — JSON Web Token (JWT).
JWT — это временный ключ. Клиент должен отправлять этот ключ серверу с каждым запросом к аутентифицированной конечной точке.
Генерирование jwt в node.js
Давайте создадим функцию
generateToken
, которая нужна нам для завершения работы над сервисом аутентификации пользователей.
Создавать JWT можно с помощью библиотеки jsonwebtoken. Найти эту библиотеку можно в npm.
Добавляем роуты в react app
Сперва добавьте компонент навигации для будущих роутов. В client/src/components создайте папку shared. Здесь будут храниться все компоненты, используемые в нескольких местах в приложении. В этой папке создайте файл Navigation.js. Файл будет хранить базовый компонент со ссылками на все страницы приложения.
Компонент навигации необходимо обернуть в компонент более высокого порядка withAuth. Это позволит проверять аутентификацию пользователя и показывать кнопки входа или выхода.
Теперь когда есть компоненты для обработки всех роутов создайте роуты. Обновите файл App.js. Конечная версия:
Здесь стоит отметить пару моментов. Импорт SecureRoute и ImplicitCallback компонентов из Okta React SDK. Компонент ImplicitCallback обрабатывает колбек из потока аутентификации и проверяет, есть ли конечная точка внутри приложения React для отлова обратного вызова из Okta.
Компонент Route из React Routeк делает ровно то, что вы думаете: он принимает путь, на который перешел пользователь, и устанавливает компонент для обработки этого роута. SecureRoute проводит дополнительные проверки авторизации пользователя, прежде чем разрешить доступ к роуту.
Из странностей здесь только роут для пути авторизации. Вместо простой установки компонента для обработки пути он запускает метод render, который рендерит компонент LoginPage и задает baseUrl из настроек.
Добавляем страницы в reactjs app
Прежде чем добавлять роуты в React app, создайте пару компонентов для обработки добавляемых роутов.
Добавьте папку components в client/src. Здесь будут храниться все ваши компоненты, это простейший способ их организации. Затем создайте папку home для компонентов домашней страницы. Сейчас компонент домашней страницы будет всего один, но в будущем их может быть больше. Добавьте в папку файл HomePage.js:
Пока что, это все, что нужно для домашней страницы. Самое важное – сделать компонент HomePage классом. Сейчас в нем всего один тег h1, но он будет страницей. То есть в нем будут другие компоненты, поэтому важно, чтобы он был компонентом-контейнером.
Далее создайте папку auth в components. Здесь будут храниться все компоненты, связанные с аутентификацией. В этой папке создайте файл LoginForm.js.
Первое, что нужно отметить – для оборачивания всей формы авторизации вы будете использовать withAuth, компонент более высокого порядка из Okta React SDK. Это добавит в компонент свойство auth, что позволит обращаться к функциям isAuthenticated и redirect компонента более высокого порядка.
Код компонента LoginForm:
Также стоит обратить внимание на импорт библиотеки OktaAuth. Это базовая библиотека для авторизации через Okta приложение, созданное ранее. Обратите внимание, что в конструкторе создался объект OktaAuth и получил переданное свойство baseUrl. Это URL для издателя, который находится в файле app.config.js.
Компонент LoginForm должен содержать другой компонент. Поэтому нужно создать файл LoginPage.js для хранения компонента. Нужно еще раз использовать компонент более высокого порядка withAuth, чтобы получить доступ к функции isAuthenticated. Контент файла LoginPage.js:
Кода меньше, чем в компоненте формы авторизации, но тут есть важные части, которые нужно озвучить.
Еще раз используется компонент более высокого порядка withAuth. Это будет повторяющаяся тема для всех компонентов, которым нужна аутентификация Okta или процесс авторизации. В этом случае он в основном используется для получения функции isAuthenticated.
Когда isAuthenticated возвращает true, значение устанавливается в состояние компонента. Далее идет проверка в методе render, чтобы определить, показывать ли компонент LoginForm или перенаправлять на страницу профиля пользователя (этот компонент мы создадим далее).
Создайте компонент ProfilePage.js в папке auth. Код файла:
Зависимости:
Так же я буду использовать для удобства несколько дополнительных утилит. Без них вполне можно обойтись:
Защита конечных точек и проверка jwt
Теперь клиентскому коду нужно отправлять JWT в каждом запросе к защищённой конечной точке.
Рекомендуется включать JWT в заголовки запросов. Обычно их включают в заголовок Authorization.
Заголовок Authorization
Теперь, на сервере, нужно создать код, представляющий собой промежуточное ПО для маршрутов express. Поместим этот код в файл isAuth.ts:
Как имперсонировать пользователя?
Имперсонация пользователей — это техника, используемая для входа в систему под видом некоего конкретного пользователя без знания его пароля.
Эта возможность весьма полезна для супер-администраторов, разработчиков или сотрудников служб поддержки. Имперсонация позволяет им решать проблемы, которые проявляются только в ходе работы пользователей с системой.
Работать с приложением от имени пользователя можно и не зная его пароля. Для этого достаточно сгенерировать JWT с правильной подписью и с необходимыми метаданными, описывающими пользователя.
Создадим конечную точку, которая может генерировать токены для входа в систему под видом конкретных пользователей. Этой конечной точкой сможет пользоваться только супер-администратор системы.
Для чала нам нужно назначить этому пользователю роль, с которой связан более высокий, чем у других пользователей, уровень привилегий. Это можно сделать множеством различных способов. Например, достаточно просто добавить поле role в сведения о пользователе, хранящиеся в базе данных.
Выглядеть это может так, как показано ниже.
Новое поле в сведениях о пользователе
Клиентская часть:
Тут я просто объясню что делать на стороне клиента, но я не буду показывать сам код, это как нибудь в другой статье, тут только объяснения.
Для начала полученный токен записываем в localStorage, делаем это примерно так:
После чего при запросе к серверу передаём наш токен, примерно так это должно выглядеть:
Как видите здесь мы в header или в заголовки запроса добавили параметр Authorization, который хранит токен, но перед ним ещё ставим слово Bearer, и после пробела, уже сам токен.
Маршруты и опции аутентификации
Создайте новую папку с названием “routes” и файлом “auth.js” внутри.
В этом файле мы использем функцию getTokenFromHeaders для получения JWT токена, который будет отправлен с клиентской стороны в заголовки запроса. Мы также создаём auth-объект c optional и required свойствами. Мы используем это позже в наших маршрутах.
В той же папке “routes” создайте файл “index.js”:
Теперь нам нужна папка “api” внутри папки “routes” с ещё одним файлом “index.js” внутри.
Модель пользователя
Для начала, я думаю, можно создать модель пользователя:
Настройка паспорта
Создайте новую папку с названием “config” и файлом “passport.js” внутри:
Подключение passport к express
Окей, с этим разобрались, теперь нужно подключить Passport к Express:
// Middlewares, которые должны быть определены до passport:
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'SECRET' }));
// Passport:
app.use(passport.initialize());
app.use(passport.session());
Привязка авторизации к пользователю
В типичном веб-приложении, учетные данные, используемые для аутентификации пользователя будет передаваться только во время авторизации. Если все в порядке, и пользователь существует, то информация о нем сохраняется в сессию, а идентификатор сессии, в свою очередь, сохраняется в cookies браузера.
Проверка авторизации
Проверку авторизации можно делать с помощью req.isAuthenticated(). Я вынесу проверку в middleware.
exports.mustAuthenticatedMw = function (req, res, next){
req.isAuthenticated()
? next()
: res.redirect('/');
};
И добавлю в routes.
App.all('private', mustAuthenticatedMw);
App.all('private/*', mustAuthenticatedMw);
Регистрация пользователей в системе
Когда в системе создаётся новый пользователь, его пароль необходимо хэшировать и сохранить в базе данных. Пароль в базе сохраняют вместе с адресом электронной почты и другими сведениями о пользователе (например, среди них может быть профиль пользователя, время регистрации и так далее).
Создаём get-запрос для возвращения текущего зашедшего пользователя
Запрос URL:
Создаём с нуля свой node-сервер
Создаём новую директорию с файлом “app.js” внутри:
Теперь установим nodemon для более лёгкой разработки.
А теперь с помощью этого запустим своё приложение “app.js”.
$ nodemon app.js
Создание post-запроса для создания пользователя
Испытываемая форма:
Ответ:
Создание роутера и контроллеров
Настало время настройки роутера. Привяжем запросы к соответствующим контроллерам:
Структура базового приложения
Перейдите в папку приложения и создайте новую папку:
Будет создано 2 папки в папке MembershipSample: api и client с Node JS и Express приложением в папке api и базовым приложением React в папке client. Структура папок теперь выглядит следующим образом:
MembershipSample
—api
—client
Для упрощения откройте 2 терминала или 2 вкладки в терминале. Один терминал для папки api приложения express и другой для папки client приложения React.
По умолчанию, приложения React и Node запускаются на порту 3000. Вам понадобится API для запуска на другом порту и прокси-сервер в клиентском приложении.
В папке api откройте файл /bin/www и измените порт, на котором будет запускаться API на 3001.
Затем настройте прокси-сервер для API в клиентском приложении, чтобы можно было вызывать /api/{resource} и прогонять его с порта 3000 на 3001. В файле client/package.json добавьте настройку proxy под name:
Не забудьте запустить npm install или yarn install для всех подпапок (api и client) для установки изменений.
Теперь можно запустить оба приложения с помощью команд running npm start или yarn start в соответствующей папке для API и клиентского приложения.
Тестирование маршрутов
Я буду использовать Postman для отсылки запросов на наш сервер.
Наш сервер принимает следующую форму:
Требования к проекту
Вот требования к проекту, которым мы будем здесь заниматься:
- Наличие базы данных, в которой будет храниться адрес электронной почты пользователя и его пароль, либо — clientId и clientSecret, либо — нечто вроде комбинации из приватного и публичного ключей.
- Использование сильного и эффективного криптографического алгоритма для шифрования пароля.
В тот момент, когда я пишу этот материал, я считаю, что лучшим из существующих криптографических алгоритмов является Argon2. Я прошу вас не использовать простые криптографические алгоритмы вроде SHA256, SHA512 или MD5.
Кроме того, предлагаю вам взглянуть на этот замечательный материал, в котором вы можете найти подробности о выборе алгоритма для хэширования паролей.
Установка зависимостей node и react
Для настройки базового приложения необходимо установить следующие инструменты:
Также понадобится профиль разработчика Okta. Для установки Node и npm следуйте инструкциям для своей ОС //nodejs.org/en/. Затем просто установите 2 npm пакета с помощью команды:
Теперь можно настраивать структуру базового приложения.
Учимся работать с аутентификацией в node используя passport.js
Перевод статьи Antonio Erdeljac: Learn how to handle authentication with Node using Passport.js
В этой статье вы узнаете, как разработать аутентификацию для вашего Node-сервера используя Passport.js. Эта статья не включает в себя фронт-энд аутентификацию. Используйте её для настройки бэк-энда вашей аутентификации (сгенерировать токен для каждого пользователя и защитить маршруты).
Также помните, что если вы застряли на каком-либо из этапов, вы можете использовать этот GitHub-репозиторий.
Create express authentication endpoints
🛠️️ In this section, you will create three endpoints that handle the application’s authentication flow:
GET /login
GET /logout
GET /callback
To manage these endpoints better, you will create them within an authentication module and export them through an Express router so that your Express application can use them.
🛠️️ To start, create an auth.js file under the project directory.
touch auth.js
Populate it with this template to define its structure:
🛠️️ Next, add the following under the Required External Modules section to import packages that are needed and load your environmental variables:
const express =require("express");const router = express.Router();const passport =require("passport");const querystring =require("querystring");require("dotenv").config();
Here’s an overview of the new modules you are using:
You’ll soon see how these modules streamline your route controller logic.
🛠️️ The first endpoint you’ll create is the GET/login one. Update the Routes Definitions section of your auth.js file as follows:
router.get("/login",
passport.authenticate("auth0",{
scope:"openid email profile"}),(req, res)=>{
res.redirect("/");});
Вывод:
В этой статье вы прочитали как делается на Node.js и express авторизация с использованием JWT токенов, надеюсь вам было полезно и вы сделали авторизацию как надо.
Итоги
Нет ничего плохого в том, чтобы полагаться на сторонние сервисы и библиотеки аутентификации. Это помогает разработчикам экономить время. Но им необходимо ещё и знать о том, на каких принципах основана работа систем аутентификации, о том, что обеспечивают функционирование таких систем.
В этом материале мы исследовали возможности JWT-аутентификации, поговорили о важности выбора хорошего криптографического алгоритма для хэширования паролей. Мы рассмотрели создание механизма имперсонации пользователей.
Сделать то же самое с помощью чего-то вроде passport.js далеко не так просто. Аутентификация — это огромная тема. Возможно, мы к ней ещё вернёмся.
Уважаемые читатели! Как вы создаёте системы аутентификации для своих Node.js-проектов?