Как пройти аутентификацию с помощью JWT.

Как пройти аутентификацию с помощью веб-токена JSON (JWT)

В этой статье вы узнаете:

• Что такое JSON Web Token (JWT).

• Как создать веб-маркер JSON (JWT).

• Как пройти аутентификацию и авторизацию с помощью веб-токена JSON (JWT)

Введение

JSON Web Token — это стандарт для создания и обмена зашифрованными данными.

Структура JWT:

JWT содержит Заголовок, Полезную нагрузку и Подпись.

Заголовок

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

Вот пример заголовка:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

А вот пример заголовка в формате JSON:

{
 “alg”: “HS256”,
 “typ”: “JWT”
}

Полезная нагрузка

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

Вот пример полезной нагрузки:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

А вот пример полезной нагрузки в формате JSON:

{
 “sub”: “1234567890”,
 “name”: “John Doe”,
 “iat”: 1516239022
}

Подпись

Третьей или последней частью JWT является подпись. Она используется для проверки владельца сообщения и, если она не была изменена по пути, для создания подписи заголовок в кодировке Base64, Payload и Secret берутся, затем подписываются по алгоритму, указанному в заголовке.

Пример подписи:

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

А вот как создается подпись:

HMACSHA256(
 base64UrlEncode(header) + “.” +
 base64UrlEncode(payload),
 your-256-bit-secret
) secret base64 encoded

Предпосылки

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

• JavaScript

• Express.js

• Некоторые навыки контроля версий

Я ожидаю, что у вас есть Node.js, любой редактор кода, платформа для тестирования API, такая как Postman, MongoDB или любая другая установленная база данных.

Настройка сервера

Создайте файл и назовите его как угодно. Я назову свой `fullauth`, а затем инициализирую папку командой ` git init `.

Теперь введите `code .` в своем терминале, чтобы открыть файл, это будет работать, только если вы используете VsCode.

Следующее, что мы хотим сделать, это инициализировать npm, выполнив `npm init -y` в нашем терминале, и у нас должен быть наш `package.json` в нашей папке.

Теперь нам нужно создать все папки и файлы, которые нам нужны, мы можем легко сделать это с помощью этой команды:

mkdir config controller middleware model routes
touch config/connect.js controller/authcontroller.js middleware/auth.js model/user.js routes/authroute.js .gitignore app.js index.js .env

И наша папка должна выглядеть так:

Установка зависимостей

И теперь мы установим все необходимые нам зависимости с помощью этой команды:

npm i mongoose dotenv express jsonwebtoken bcryptjs nodemon

Теперь мы добавим `“dev”: “nodemon index.js”` в скрипты в файле `package.json` и `”type”: “module”`, первое для автоматического перезапуска сервера, а второе для включите модули ES6, наш файл package.json должен выглядеть так:

{
 “name”: “fullauth”,
 “version”: “1.0.0”,
 “description”: “”,
 “main”: “index.js”,
 “scripts”: {
 “test”: “echo \”Error: no test specified\” && exit 1",
 “dev”: “nodemon index.js”
 },
 “keywords”: [],
 “author”: “”,
 “license”: “ISC”,
 “dependencies”: {
 “bcryptjs”: “².4.3”,
 “dotenv”: “¹⁶.0.3”,
 “express”: “⁴.18.2”,
 “jsonwebtoken”: “⁸.5.1”,
 “mongoose”: “⁶.7.0”,
 “nodemon”: “².0.20”
 },
 “type”: “module”
}

Начиная

Создание сервера Node.js

Подключение к локальной MongoDB

Мы добавим это в файл `connect.js`:

import mongoose from “mongoose”;
import “dotenv/config”;
const connect = {
 connectToLocalMongoDB : () =>{
 mongoose
 .connect(process.env.MONGO_URI_LOCAL, {
 useNewUrlParser: true,
 useUnifiedTopology: true,
 })
 .then(() => console.log(“DB Connected!”))
 .catch((err) => {
 console.log(“not able to connect to database”+ err);
 });
 }
}
export default connect;

Добавление `MONGO_URI_LOCAL` в файл `.env`:

MONGO_URI_LOCAL=mongodb://127.0.0.1:27017/fullauth

Обновление файла `app.js`:

import express from ‘express’
import connect from “./config/connect.js”;
const app = express()
app.use(
 express.urlencoded({
 extended: false,
 })
 );
 
app.use(express.json());
connect.connectToLocalMongoDB()
export default app

Запуск сервера с файлом `index.js`

Мы добавим это в наш файл index.js:

import “dotenv/config”;
import app from “./app.js”;
const PORT = process.env.PORT || 8080
app.get(‘/’, (req, res) =>{
 res.send(“Welcome to Full authentication app”)
})
app.listen(PORT, () =>{
 console.log(`app is listening on port ${PORT}`)
})

Добавление PORT в файл .env:

PORT=8080

Теперь вы можете запустить `npm run dev`, чтобы запустить сервер.

Создание модели пользователя в файле `user.js`:

Мы создадим модель пользователя в файле `user.js`, это то, что мы будем вызывать для выполнения операций с базой данных:

import mongoose from “mongoose”;
const User = new mongoose.Schema({
 first_name: {type: String, default: null},
 last_name: {type: String, default: null},
 email: {type: String, default: null},
 password: {type: String, default: null},
})
export default mongoose.model(“user”, User);

Реализация контроллеров SignUp и SignIn

Мы хотим реализовать контроллер signUp, конечная точка `/signup` будет подключена к этому контроллеру, чтобы новый пользователь мог зарегистрироваться.

Что мы будем делать:

- проверить, существует ли пользователь
- проверить, не пусто ли какое-либо поле
- создать нового пользователя и JWT

Импорт зависимостей в файл `authcontroller.js`:

import “dotenv/config”;
import user from “../model/user.js”;
import bcrypt from “bcryptjs”;
import jwt from “jsonwebtoken”;

Создание контроллера SignUp в файле `authcontroller.js`:

export const signUp = async (req, res) =>{
 const { firstname, lastname, email, password } = req.body
const oldUser = await user.findOne({email: email})
if(oldUser){
 return res.status(424).json({
 status: false,
 message: “user already exists”
 })
 }
if(!(firstname && lastname && email && password)){
 return res.status(424).json({
 status: false,
 message: “all fields are required”
 })
 }
 
 if( oldUser == null ){
 const encrptedPassword = await bcrypt.hash(password, 12);
const newUser = await user.create({
 first_name: firstname,
 last_name: lastname,
 email: email,
 password: encrptedPassword,
 })
const token = jwt.sign({id: newUser._id, }, process.env.JWT_SECRET_KEY,{expiresIn: “2d”});
return res.status(200).json({
 status: true,
 message: “Registration successful”,
 data: {
 token: token
 }
 })
 }
}

Что мы сделали :

1. Мы импортировали все зависимости, которые будем использовать

2. Мы проверили, существует ли пользователь

3. Создаем нового пользователя, если он не существует.

4. Мы использовали Jwt.sign для создания нового токена, метод `.sign` принимает параметры, которые включают Payload, он содержит утверждения, которые являются заявлениями о пользователе и дополнительными данными, второй параметр секретный ключ, который мы будем использовать для создания уникального хэша, а затем последний параметр, который я добавил, — это параметр подписи, устанавливающий время истечения срока действия на 2 дня.

Создание контроллера SignIn в файле `authcontroller.js`:

export const signIn = async (req, res) =>{
 try {
 const { email, password } = req.body;
 
 const oldUser = await user.findOne({email: email})
 
 if(!(email && password)){
 return res.status(424).json({
 status: false,
 message: “all fields are required”
 })
 }
 
 if( oldUser == null ){
 return res.status(424).json({
 status: false,
 message: “User does not exist”
 })
 }
 
 if( oldUser && (await bcrypt.compare(password, oldUser.password))){
 const token = jwt.sign({id: oldUser._id, email:email }, process.env.JWT_SECRET_KEY,{expiresIn: “2d”});
 return res.status(200).json({
 status: true,
 message: “Login Successful”,
 data:{
 token: token
 }
 })
 }else{
 return res.status(424).json({
 status: false,
 message: “Incorrect email or password”
 }) 
 } 
 } catch (error) {
 return res.status(500).json({
 status: false,
 message: “An error occurred”
 }) 
 }
}

Что мы сделали :

1. Проверьте, существует ли пользователь, используя указанный адрес электронной почты.

2. Если пользователь существует и пароль совпадает с паролем в базе данных, создайте JWT с идентификатором и адресом электронной почты в качестве полезной нагрузки, секретом в нашем файле `.env` в качестве подписи и установите срок действия 2 дня.

3. Вернуть ответ, который включает JWT.

Создание промежуточного ПО, которое проверяет и декодирует JWT в файле `auth.js`:

import jwt from “jsonwebtoken”;
export const verifyToken = async (req, res, next) =>{
 try {
 const token = req.body.token || req.query.token || req.headers[‘access-token’]
 
 if(!token){
 return res.status(403).json({
 status: false,
 message: `A token is required for authentication`
 })
 }
const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY)
req.user = decoded
 
 } catch (error) {
 return res.status(401).json({
 status: false,
 message: `Invalid Token`
 })
 }
 return next();
}

Что мы сделали :

1. Проверьте наличие JWT в теле запроса, запросе или заголовке с помощью «токена доступа» и ключа.

2. Если предоставляется JWT, декодируйте его с помощью метода `jwt.verify`, этот метод принимает два параметра, то есть JWT и секретный ключ, который использовался для его подписи.

3. Назначьте декодированный объект ` req.user `, чтобы маршруты после аутентификации могли его оценить.

4. Верните «Неверный код», если срок действия кода истек или он был изменен.

Создание контроллера, который приветствует аутентифицированного пользователя в файле `authcontroller.js`:

export const greetUser = async (req, res) =>{
 try {
 const id = req.user.id;
const oldUser = await user.findById(id);
if( oldUser ){
 return res.status(200).json({
 status: true,
 message: `Hello ${oldUser.first_name}`
 })
 }else{
 return res.status(498).json({
 status: false,
 message: `Invalid Id`
 })
 }
} catch (error) {
 return res.status(500).json({
 status: false,
 message: “An error occurred”
 }) 
 }
}

Что мы сделали :

1. Получите идентификатор от `req.user`, когда пользователь аутентифицирован

2. Найти пользователя по идентификатору из базы данных

3. Вернуть ответное приветствие пользователя с именем в базе данных.

Создание и использование маршрутов

Следующее, что мы хотим сделать, это создать конечные точки, подключить их к контроллерам в файле `authroute.js` и импортировать их в файл `app.js`, чтобы он был доступен.

Создание и подключение маршрутов в файле `authroute.js`:

import { Router } from “express”;
import { signUp, signIn, greetUser} from “../controller/authcontroller.js”;
import { verifyToken } from “../middleware/auth.js”;
const router = Router();
router.route(“/signup”).post(signUp);
router.route(“/signIn”).post(signIn);
router.route(“/greet-user”).get(verifyToken, greetUser)
export default router;

Импортируйте и используйте маршруты в файле `app.js`:

import authroute from “./routes/authroute.js”
app.use(authroute)

Ваш файл `app.js` должен выглядеть так:

import express from ‘express’
import connect from “./config/connect.js”;
import authroute from “./routes/authroute.js”
const app = express()
app.use(
 express.urlencoded({
 extended: false,
 })
 );
 
app.use(express.json());
app.use(authroute)
connect.connectToLocalMongoDB()
export default app

Тестирование конечных точек в Postman

Тестирование регистрации Enpoint

Запрос :

Ответ :

Тестирование входа в систему

Запрос :

Ответ :

Тестирование приветствия пользователя

Вставка JWT в заголовок:

Запрос :

Ответ :

Заключение

Итак, мы зашли так далеко, и я думаю, вы должны были научиться

– Создать JWT
– Использовать JWT в точках регистрации и входа
– Извлечь JWT из заголовка и расшифровать его
– Как использовать объект из JWT, когда пользователь проходит аутентификацию

«Совершенство достигается не тогда, когда нечего добавить, а тогда, когда нечего больше убрать». — Антуан де Сент-Экзюпери

Узнать больше о JWT можно Здесь

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

Получить полный код можно Здесь

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