Jak uwierzytelnić i autoryzować użytkownika za pomocą JWT w NodeJS

Uwierzytelnianie i autoryzacja to podstawowe pojęcie bezpieczeństwa komputerowego. Używasz swoich danych uwierzytelniających (takich jak nazwa użytkownika i hasło), aby potwierdzić swoją tożsamość i zidentyfikować się jako zarejestrowany użytkownik, a następnie uzyskać dodatkowe uprawnienia.

Dotyczy to również sytuacji, gdy logujesz się do usług online za pomocą swoich kont Facebook lub Google.

W tym artykule zamierzamy zbudować interfejs API Nodejs z uwierzytelnianiem JWT (JSON Web Tokens). Narzędzia, których użyjemy w tym samouczku to:

  • Expressjs
  • Baza danych MongoDB
  • Mangusta
  • Dotenv
  • Bcryptjs
  • Jsonwebtoken

Uwierzytelnianie vs. Upoważnienie

Co to jest uwierzytelnianie?

Uwierzytelnianie to proces identyfikacji użytkowników poprzez uzyskanie poświadczeń, takich jak adres e-mail, hasło i tokeny. Podane poświadczenia są porównywane z poświadczeniami zarejestrowanego użytkownika, które są dostępne w pliku lokalnego systemu komputerowego lub dowolnych baz danych. Jeśli podane poświadczenia są zgodne z dostępnymi danymi w bazie danych, proces uwierzytelniania jest zakończony, a użytkownik uzyskuje dostęp do zasobów.

Co to jest autoryzacja?

Autoryzacja następuje po uwierzytelnieniu. Każda autoryzacja musi mieć proces uwierzytelniania. Jest to proces umożliwiający użytkownikom dostęp do zasobów z systemów lub strony internetowej. W tym samouczku będziemy autoryzować zalogowanego użytkownika dostęp do danych użytkownika. Jeśli użytkownik nie jest zalogowany, nie będzie mógł korzystać z dostępu do danych.

Najlepszymi przykładami autoryzacji są platformy mediów społecznościowych, takie jak Facebook i Twitter. Nie możesz uzyskać dostępu do treści w mediach społecznościowych bez posiadania konta.

Innym przykładem autoryzacji jest zawartość oparta na subskrypcji, uwierzytelnianie można wykonać, logując się do witryny, ale nie będziesz mieć autoryzacji do dostępu do zawartości, dopóki nie dokonasz subskrypcji.

Warunek wstępny

Zanim przejdziesz dalej, zakładam, że masz podstawową wiedzę na temat Javascript i MongoDB oraz dobrą znajomość Nodejs.

Upewnij się, że masz zainstalowany węzeł i npm na komputerze lokalnym. Aby sprawdzić, czy na komputerze są zainstalowane node i npm, otwórz wiersz polecenia i wpisz node -v i npm -v. Powinno to pokazać następujący wynik.

Twoje wersje mogą różnić się od moich. NPM jest automatycznie pobierany z węzłem. Jeśli jeszcze go nie pobrałeś, pobierz go z Strona internetowa NodeJS.

Do pisania kodu potrzebne będzie IDE (zintegrowane środowisko programistyczne). W tym samouczku używam edytora kodu VS. Jeśli masz inny, możesz go również użyć. Jeśli nie masz zainstalowanego IDE na swoim komputerze, możesz je pobrać ze strony Witryna programu Visual Studio. Pobierz go w oparciu o system lokalny.

Konfiguracja projektu

Utwórz nazwę folderu nodeapi w dowolnym miejscu na komputerze lokalnym, a następnie otwórz ją za pomocą vs-code. Otwórz terminal vs-code, a następnie zainicjuj menedżera pakietów węzłów, wpisując.

npm init -y

Upewnij się, że jesteś w katalogu nodeapi.

Powyższe polecenie utworzy plik package.json, który będzie zawierał wszystkie zależności, których będziemy używać w tym projekcie.

Teraz pobierzemy wszystkie wyżej wymienione pakiety, teraz wpisz i wpisz je w terminalu.

npm install express dotenv jsonwebtoken mongoose bcryptjs

Teraz będziesz mieć pliki i foldery, jak pokazano poniżej.

Tworzenie serwera i podłączanie bazy danych

Teraz utwórz plik o nazwie index.js i folder o nazwie config. Wewnątrz config, utwórz dwa pliki o nazwie conn.js, aby połączyć się z bazą danych i config.env, aby zadeklarować zmienne środowiskowe. Wpisz podany poniżej kod w odpowiednich plikach.

index.js

const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 

//Creating an app from express
const app = express();

//Using express.json to get request of json data
app.use(express.json());



//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

Jeśli używasz dotenv, skonfiguruj go w pliku index.js przed wywołaniem innych plików, które używają zmiennych środowiskowych.

conn.js

const mongoose = require('mongoose');

mongoose.connect(process.env.URI, 
    { useNewUrlParser: true,
     useUnifiedTopology: true })
    .then((data) => {
        console.log(`Database connected to ${data.connection.host}`)
})

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000

Używam mongo-DB Atlas URI, możesz również użyć localhost.

Tworzenie modeli i tras

Model to układ danych w bazie danych Mongo-DB, który będzie przechowywany jako dokument JSON. Aby stworzyć model, użyjemy schematu mangusty.

Routing odnosi się do sposobu, w jaki aplikacja odpowiada na żądania klientów. Do tworzenia tras użyjemy funkcji ekspresowego routera.

Metody routingu zwykle przyjmują dwa argumenty. Pierwsza to trasa, a druga to funkcja zwrotna, która definiuje, co ta trasa zrobi na żądanie klienta.

W razie potrzeby przyjmuje również trzeci argument jako funkcję oprogramowania pośredniego, na przykład w procesie uwierzytelniania. Ponieważ budujemy uwierzytelniony interfejs API, użyjemy również funkcji oprogramowania pośredniczącego do autoryzacji i uwierzytelniania użytkowników.

Teraz utworzymy dwa foldery o nazwach tras i modeli. Wewnątrz tras utwórz plik o nazwie userRoute.js, a wewnątrz folderu modeli utwórz plik o nazwie userModel.js. Po utworzeniu plików napisz następujący kod w odpowiednich plikach.

userModel.js

const mongoose = require('mongoose');

//Creating Schema using mongoose
const userSchema = new mongoose.Schema({
    name: {
        type:String,
        required:true,
        minLength:[4,'Name should be minimum of 4 characters']
    },
    email:{
        type:String,
        required:true,
        unique:true,
    },
    password:{
        type:String,
        required:true,
        minLength:[8,'Password should be minimum of 8 characters']
    },
    token:{
        type:String
    }
})

//Creating models
const userModel = mongoose.model('user',userSchema);
module.exports = userModel;

userRoute.js

const express = require('express');
//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating register route
route.post('/register',(req,res)=>{

})
//Creating login routes
route.post('/login',(req,res)=>{

})

//Creating user routes to fetch users data
route.get('/user',(req,res)=>{

})

Implementacja funkcjonalności tras i tworzenie tokenów JWT

Co to jest token JWT?

Tokeny internetowe JSON (JWT) to biblioteka JavaScript, która tworzy i weryfikuje tokeny. Jest to otwarty standard używany do dzielenia się informacjami między dwiema stronami – klientem i serwerem. Wykorzystamy dwie funkcje JWT. Pierwsza funkcja to znak, aby utworzyć nowy token, a druga funkcja to weryfikacja w celu weryfikacji tokena.

Co to jest bcryptjs?

Bcryptjs to funkcja mieszająca stworzona przez Nielsa Provosa i Davida Mazièresa. Używa algorytmu haszującego do haszowania hasła. Posiada dwie najpopularniejsze funkcje, z których będziemy korzystać w tym projekcie. Pierwsza funkcja bcryptjs to skrót do generowania wartości skrótu, a druga funkcja to funkcja porównania do porównywania haseł.

Zaimplementuj funkcjonalność trasy

Funkcja wywołania zwrotnego w routingu przyjmuje trzy argumenty: żądanie, odpowiedź i następną funkcję. Następny argument jest opcjonalny; przekaż to tylko wtedy, gdy tego potrzebujesz. Te argumenty powinny znajdować się w żądaniu, odpowiedzi i następnej kolejności. Teraz zmodyfikuj pliki userRoute.js, config.env i index.js za pomocą następujących kodów.

userRoute.js

//Requiring all the necessary files and libraries
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating register route
route.post("/register", async (req, res) => {

    try {
        const { name, email, password } = req.body;
        //Check emptyness of the incoming data
        if (!name || !email || !password) {
            return res.json({ message: 'Please enter all the details' })
        }

        //Check if the user already exist or not
        const userExist = await userModel.findOne({ email: req.body.email });
        if (userExist) {
            return res.json({ message: 'User already exist with the given emailId' })
        }
        //Hash the password
        const salt = await bcrypt.genSalt(10);
        const hashPassword = await bcrypt.hash(req.body.password, salt);
        req.body.password = hashPassword;
        const user = new userModel(req.body);
        await user.save();
        const token = await jwt.sign({ id: user._id }, process.env.SECRET_KEY, {
            expiresIn: process.env.JWT_EXPIRE,
        });
        return res.cookie({ 'token': token }).json({ success: true, message: 'User registered successfully', data: user })
    } catch (error) {
        return res.json({ error: error });
    }

})
//Creating login routes
route.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body;
        //Check emptyness of the incoming data
        if (!email || !password) {
            return res.json({ message: 'Please enter all the details' })
        }
        //Check if the user already exist or not
        const userExist = await userModel.findOne({email:req.body.email});
        if(!userExist){
            return res.json({message:'Wrong credentials'})
        }
        //Check password match
        const isPasswordMatched = await bcrypt.compare(password,userExist.password);
        if(!isPasswordMatched){
            return res.json({message:'Wrong credentials pass'});
        }
        const token = await jwt.sign({ id: userExist._id }, process.env.SECRET_KEY, {
            expiresIn: process.env.JWT_EXPIRE,
        });
        return res.cookie({"token":token}).json({success:true,message:'LoggedIn Successfully'})
    } catch (error) {
        return res.json({ error: error });
    }

})

//Creating user routes to fetch users data
route.get('/user', async (req, res) => {
    try {
        const user  = await userModel.find();
        if(!user){
            return res.json({message:'No user found'})
        }
        return res.json({user:user})
    } catch (error) {
        return res.json({ error: error });  
    }
})

module.exports = route;

Jeśli używasz funkcji Async, użyj bloku try-catch, w przeciwnym razie zgłosi nieobsługiwany błąd odrzucenia obietnicy.

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000
SECRET_KEY = KGGK>HKHVHJVKBKJKJBKBKHKBMKHB
JWT_EXPIRE = 2d

index.js

const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 
require('./config/conn');
//Creating an app from express
const app = express();
const route = require('./routes/userRoute');

//Using express.json to get request of json data
app.use(express.json());
//Using routes

app.use('/api', route);

//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

Tworzenie oprogramowania pośredniego do uwierzytelniania użytkownika

Co to jest oprogramowanie pośredniczące?

Oprogramowanie pośredniczące to funkcja, która ma dostęp do żądania, obiektu odpowiedzi i następnej funkcji w cyklu żądanie-odpowiedź. Następna funkcja jest wywoływana po zakończeniu wykonywania funkcji. Jak wspomniałem powyżej, użyj next(), gdy musisz wykonać inną funkcję wywołania zwrotnego lub funkcję oprogramowania pośredniczącego.

Teraz utwórz folder o nazwie middleware, a wewnątrz niego utwórz nazwę pliku jako auth.js i napisz następujący kod.

auth.js

const userModel = require('../models/userModel');
const jwt = require('jsonwebtoken');
const isAuthenticated = async (req,res,next)=>{
    try {
        const {token} = req.cookies;
        if(!token){
            return next('Please login to access the data');
        }
        const verify = await jwt.verify(token,process.env.SECRET_KEY);
        req.user = await userModel.findById(verify.id);
        next();
    } catch (error) {
       return next(error); 
    }
}

module.exports = isAuthenticated;

Teraz zainstaluj bibliotekę cookie-parser, aby skonfigurować cookieParser w swojej aplikacji. cookieParser pomaga uzyskać dostęp do tokena przechowywanego w pliku cookie. Jeśli nie masz skonfigurowanego pliku cookieParser w swojej aplikacji nodejs, nie będziesz mieć dostępu do plików cookie z nagłówków obiektu żądania. Teraz napisz w terminalu, aby pobrać parser plików cookie.

npm i cookie-parser

Teraz masz zainstalowany plik cookieParser. Skonfiguruj swoją aplikację, modyfikując plik index.js i dodaj oprogramowanie pośredniczące do trasy „/user/”.

plik index.js

const cookieParser = require('cookie-parser');
const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 
require('./config/conn');
//Creating an app from express
const app = express();
const route = require('./routes/userRoute');

//Using express.json to get request of json data
app.use(express.json());
//Configuring cookie-parser
app.use(cookieParser()); 

//Using routes
app.use('/api', route);

//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

userRoute.js

//Requiring all the necessary files and libraries
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const isAuthenticated = require('../middleware/auth');

//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating user routes to fetch users data
route.get('/user', isAuthenticated, async (req, res) => {
    try {
        const user = await userModel.find();
        if (!user) {
            return res.json({ message: 'No user found' })
        }
        return res.json({ user: user })
    } catch (error) {
        return res.json({ error: error });
    }
})

module.exports = route;

Trasa „/user” jest dostępna tylko wtedy, gdy użytkownik jest zalogowany.

Sprawdzanie interfejsów API w POSTMAN

Zanim sprawdzisz interfejsy API, musisz zmodyfikować plik package.json. Dodaj następujące wiersze kodu.

"scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },

Serwer można uruchomić, wpisując npm start, ale zostanie uruchomiony tylko raz. Aby Twój serwer działał podczas zmiany plików, będziesz potrzebować nodemon. Pobierz go, wpisując w terminalu

npm install -g nodemon

Flaga -g pobierze nodemon globalnie na twój lokalny system. Nie musisz go ciągle pobierać dla każdego nowego projektu.

Aby uruchomić serwer, wpisz npm run dev w terminalu. Otrzymasz następujący wynik.

Wreszcie Twój kod jest gotowy, a serwer działa poprawnie, idź do listonosza i sprawdź, czy działa.

Co to jest listonosz?

POSTMAN to oprogramowanie do projektowania, budowania, rozwijania i testowania API.

Jeśli nie pobrałeś listonosza na swój komputer, pobierz go z strona listonosza.

Teraz otwórz listonosza i utwórz nazwę kolekcji nodeAPItest, a wewnątrz niej utwórz trzy żądania: register, login i user. Powinieneś mieć następujące pliki.

Gdy wyślesz dane JSON do „localhost:5000/api/register”, otrzymasz następujący wynik.

Ponieważ tworzymy i zapisujemy tokeny w plikach cookie również podczas rejestracji, możesz uzyskać dane użytkownika, gdy zażądasz trasy „localhost:5000/api/user”. Możesz sprawdzić resztę próśb na POSTMAN.

Jeśli chcesz kompletny kod, możesz go otrzymać od my konto na githubie.

Wniosek

W tym samouczku dowiedzieliśmy się, jak zastosować uwierzytelnianie do interfejsu NodeJS API za pomocą tokenów JWT. Upoważniliśmy również użytkowników do dostępu do danych użytkownika.

SZCZĘŚLIWEGO KODOWANIA!