Obsluha ověřování v Express.js

Úvod

V tomto článku vytvoříme jednoduchou aplikaci, která ukáže, jak můžete zacházet s ověřováním v Express.js. Vzhledem k tomu, že pro návrh uživatelského rozhraní budeme používat některé základní syntaxe ES6 a framework Bootstrap, mohlo by vám pomoci, pokud máte nějaké základní znalosti o těchto technologiích.

I když možná budete muset použít databázi v reálné aplikaci, protože tento článek musíme zachovat jednoduchý, nebudeme používat žádné databáze ani metody ověřování e-mailů, jako je odesílání e-mailu s ověřovacím kódem.

Nastavení projektu

Nejprve vytvořte novou složku s názvem, řekněme, simple-web-app . Pomocí terminálu přejdeme do této složky a vytvoříme kostru projektu Node.js:

$ npm init

Nyní můžeme nainstalovat také Express:

$ npm install --save express

Abychom to zjednodušili, budeme používat vykreslovací modul na straně serveru s názvem Handlebars. Tento engine bude vykreslovat naše HTML stránky na straně serveru, díky čemuž nebudeme potřebovat žádný jiný front-tend framework, jako je Angular nebo React.

Pokračujme a nainstalujme express-handlebars :

$ npm install --save express-handlebars

Budeme také používat dva další balíčky Express middlewaru (body-parser a cookie-parser ) analyzovat těla požadavků HTTP a analyzovat požadované soubory cookie pro ověření:

$ npm install --save body-parser cookie-parser

Implementace

Aplikace, kterou se chystáme vytvořit, bude obsahovat "chráněnou" stránku, kterou budou moci navštívit pouze přihlášení uživatelé, jinak budou přesměrováni na domovskou stránku a budou vyzváni k přihlášení nebo registraci.

Chcete-li začít, importujme knihovny, které jsme dříve nainstalovali:

const express = require('express');
const exphbs = require('express-handlebars');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');

Budeme používat nativní crypto uzlu modul pro hashování hesel a pro generování autentizačního tokenu – to bude rozvedeno o něco později v článku.

Dále vytvořte jednoduchou aplikaci Express a nakonfigurujte middleware, který jsme importovali, spolu s modulem Handlebars:

const app = express();

// To support URL-encoded bodies
app.use(bodyParser.urlencoded({ extended: true }));

// To parse cookies from the HTTP Request
app.use(cookieParser());

app.engine('hbs', exphbs({
    extname: '.hbs'
}));

app.set('view engine', 'hbs');

// Our requests hadlers will be implemented here...

app.listen(3000);

Ve výchozím nastavení by přípona šablony v Handlebars měla být .handlebars . Jak můžete vidět v tomto kódu, nakonfigurovali jsme náš modul šablon řídítek tak, aby podporoval soubory s .hbs kratší prodloužení. Nyní vytvoříme několik souborů šablon:

layouts složky uvnitř view složka bude obsahovat vaše hlavní rozložení, které poskytne základní HTML pro další šablony.

Pojďme vytvořit main.hbs , naše hlavní stránka obálky:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>

        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    </head>
    <body>

        <div class="container">
            {{{body}}}
        </div>

        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    </body>
</html>

Ostatní šablony se vykreslí uvnitř {{{body}}} tag této šablony. V tomto rozložení máme importovaný HTML standard a požadované CSS a JS soubory pro Bootstrap.

Po dokončení hlavního obalu vytvoříme home.hbs stránka, kde budou uživatelé vyzváni k přihlášení nebo registraci:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">Simple Authentication App</a>
</nav>

<div style="margin-top: 30px">
    <a class="btn btn-primary btn-lg active" href="/login">Login</a>
    <a class="btn btn-primary btn-lg active" href="/register">Register</a>
</div>

Poté vytvoříme obslužnou rutinu požadavku ke kořenové cestě cesty (/ ) k vykreslení domovské šablony.

app.get('/', function (req, res) {
    res.render('home');
});

Spusťte naši aplikaci a přejděte na http://localhost:3000 :

Registrace účtu

Informace o účtu jsou shromažďovány prostřednictvím registration.hbs stránka:

<div class="row justify-content-md-center" style="margin-top: 30px">
    <div class="col-md-4">

        {{#if message}}
            <div class="alert {{messageClass}}" role="alert">
                {{message}}
            </div>
        {{/if}}

        <form method="POST" action="/register">
            <div class="form-group">
                <label for="firstNameInput">First Name</label>
                <input name="firstName" type="text" class="form-control" id="firstNameInput">
            </div>

            <div class="form-group">
                <label for="lastNameInput">Last Name</label>
                <input name="firstName" type="text" class="form-control" id="lastNameInput">
            </div>

            <div class="form-group">
                <label for="emailInput">Email address</label>
                <input name="email" type="email" class="form-control" id="emailInput" placeholder="Enter email">
            </div>

            <div class="form-group">
                <label for="passwordInput">Password</label>
                <input name="password" type="password" class="form-control" id="passwordInput" placeholder="Password">
            </div>

            <div class="form-group">
                <label for="confirmPasswordInput">Confirm Password</label>
                <input name="confirmPassword" type="password" class="form-control" id="confirmPasswordInput"
                    placeholder="Re-enter your password here">
            </div>

            <button type="submit" class="btn btn-primary">Login</button>
        </form>
    </div>
</div>

V této šabloně jsme vytvořili formulář s registračními poli uživatele, což jsou Jméno, Příjmení, E-mailová adresa, Heslo a Potvrzení hesla, a nastavili jsme naši akci jako /register trasa. Máme také pole zpráv, ve kterém zobrazíme chybové zprávy a zprávy o úspěchu, například pokud se hesla neshodují atd.

Pojďme vytvořit popisovač požadavku pro vykreslení šablony registrace, když uživatel navštíví http://localhost:3000/register :

app.get('/register', (req, res) => {
    res.render('register');
});

Zdarma e-kniha:Git Essentials

Prohlédněte si našeho praktického průvodce učením Git s osvědčenými postupy, průmyslově uznávanými standardy a přiloženým cheat sheetem. Přestaňte používat příkazy Google Git a skutečně se naučte to!

Z bezpečnostních důvodů je dobré heslo hašovat pomocí silného hašovacího algoritmu, jako je SHA256 . Hašováním hesel zajišťujeme, že i když může být naše databáze hesel prolomena, hesla tam nezůstanou jednoduše na očích v textovém formátu.

Ještě lepší metodou než jen jednoduché hašování je použití salt, jako u algoritmu bcrypt. Další informace o zabezpečení ověřování najdete v tématu Implementace ověřování uživatele správným způsobem. V tomto článku však věci trochu zjednodušíme.

const crypto = require('crypto');

const getHashedPassword = (password) => {
    const sha256 = crypto.createHash('sha256');
    const hash = sha256.update(password).digest('base64');
    return hash;
}

Když uživatel odešle registrační formulář, zobrazí se POST požadavek bude odeslán na /register cesta.

Jak již bylo řečeno, nyní musíme tento požadavek zpracovat s informacemi z formuláře a zachovat našeho nově vytvořeného uživatele. Typicky se to provádí ponecháním uživatele v databázi, ale kvůli jednoduchosti uložíme uživatele do pole JavaScript.

Vzhledem k tomu, že každý restart serveru znovu inicializuje pole, zakódujeme uživatele pro účely testování, aby byl pokaždé inicializován:

const users = [
    // This user is added to the array to avoid creating a new user on each restart
    {
        firstName: 'John',
        lastName: 'Doe',
        email: '[email protected]',
        // This is the SHA256 hash for value of `password`
        password: 'XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg='
    }
];

app.post('/register', (req, res) => {
    const { email, firstName, lastName, password, confirmPassword } = req.body;

    // Check if the password and confirm password fields match
    if (password === confirmPassword) {

        // Check if user with the same email is also registered
        if (users.find(user => user.email === email)) {

            res.render('register', {
                message: 'User already registered.',
                messageClass: 'alert-danger'
            });

            return;
        }

        const hashedPassword = getHashedPassword(password);

        // Store user into the database if you are using one
        users.push({
            firstName,
            lastName,
            email,
            password: hashedPassword
        });

        res.render('login', {
            message: 'Registration Complete. Please login to continue.',
            messageClass: 'alert-success'
        });
    } else {
        res.render('register', {
            message: 'Password does not match.',
            messageClass: 'alert-danger'
        });
    }
});

Přijaté email , firstName , lastName , password a confirmPassword jsou ověřené – hesla se shodují, e-mail ještě není zaregistrován atd.

Pokud je každé ověření úspěšné, zahašujeme heslo a uložíme informace do pole a přesměrujeme uživatele na přihlašovací stránku. V opačném případě znovu vykreslíme registrační stránku s chybovou zprávou.

Nyní se podíváme na /register koncový bod pro ověření, že funguje správně:

Přihlášení k účtu

S registrací mimo cestu můžeme implementovat funkci přihlášení. Začněme vytvořením login.hbs stránka:

<div class="row justify-content-md-center" style="margin-top: 100px">
    <div class="col-md-6">

        {{#if message}}
            <div class="alert {{messageClass}}" role="alert">
                {{message}}
            </div>
        {{/if}}

        <form method="POST" action="/login">
            <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <input name="email" type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1">Password</label>
                <input name="password" type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
            </div>
            <button type="submit" class="btn btn-primary">Login</button>
        </form>
    </div>
</div>

A pak vytvoříme obslužnou rutinu také pro tento požadavek:

app.get('/login', (req, res) => {
    res.render('login');
});

Tento formulář odešle POST požadavek na /login když uživatel odešle formulář. Další věcí, kterou uděláme, je odeslání ověřovacího tokenu pro přihlášení. Tento token bude použit k identifikaci uživatele a pokaždé, když odešle požadavek HTTP, bude tento token odeslán jako soubor cookie:

const generateAuthToken = () => {
    return crypto.randomBytes(30).toString('hex');
}

Pomocí naší pomocné metody můžeme vytvořit obslužnou rutinu požadavku pro přihlašovací stránku:

// This will hold the users and authToken related to users
const authTokens = {};

app.post('/login', (req, res) => {
    const { email, password } = req.body;
    const hashedPassword = getHashedPassword(password);

    const user = users.find(u => {
        return u.email === email && hashedPassword === u.password
    });

    if (user) {
        const authToken = generateAuthToken();

        // Store authentication token
        authTokens[authToken] = user;

        // Setting the auth token in cookies
        res.cookie('AuthToken', authToken);

        // Redirect user to the protected page
        res.redirect('/protected');
    } else {
        res.render('login', {
            message: 'Invalid username or password',
            messageClass: 'alert-danger'
        });
    }
});

V tomto obslužném programu požadavku je to mapa nazvaná authTokens se používá k uložení ověřovacích tokenů jako klíče a odpovídajícího uživatele jako hodnoty, což umožňuje jednoduché vyhledání tokenu pro uživatele. K ukládání těchto tokenů můžete použít databázi jako Redis nebo opravdu jakoukoli databázi – pro jednoduchost používáme tuto mapu.

Stiskněte /login koncový bod, přivítá nás:

Ještě jsme ale úplně neskončili. Budeme muset vložit uživatele do požadavku přečtením authToken z cookies po obdržení žádosti o přihlášení. Nad všemi obslužnými programy a pod cookie-parser middleware, pojďme vytvořit vlastní middleware pro vkládání uživatelů do požadavků:

app.use((req, res, next) => {
    // Get auth token from the cookies
    const authToken = req.cookies['AuthToken'];

    // Inject the user to the request
    req.user = authTokens[authToken];

    next();
});

Nyní můžeme použít req.user uvnitř našich obslužných programů pro kontrolu, zda je uživatel ověřen pomocí tokenu.

Nakonec vytvoříme obslužný program pro vykreslení chráněné stránky - protected.hbs :

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">Protected Page</a>
</nav>

<div>
    <h2>This page is only visible to logged in users</h2>
</div>

A obslužný program pro stránku:

app.get('/protected', (req, res) => {
    if (req.user) {
        res.render('protected');
    } else {
        res.render('login', {
            message: 'Please login to continue',
            messageClass: 'alert-danger'
        });
    }
});

Jak vidíte, můžete použít req.user zkontrolovat, zda je uživatel ověřen. Pokud je tento objekt prázdný, uživatel není ověřen.

Dalším způsobem, jak vyžadovat autentizaci na trasách, je implementovat ji jako middleware, který pak lze aplikovat na trasy přímo, jak jsou definovány pomocí app objekt:

const requireAuth = (req, res, next) => {
    if (req.user) {
        next();
    } else {
        res.render('login', {
            message: 'Please login to continue',
            messageClass: 'alert-danger'
        });
    }
};

app.get('/protected', requireAuth, (req, res) => {
    res.render('protected');
});

Autorizační strategie lze také implementovat tímto způsobem přidělováním rolí uživatelům a následnou kontrolou správných oprávnění předtím, než uživatel přistoupí na stránku.

Závěr

Autentizace uživatele v Express je velmi jednoduchá a přímočará. Použili jsme nativní crypto Node modul pro hašování hesel registrovaných uživatelů jako základní bezpečnostní prvek a vytvořil chráněnou stránku, viditelnou pouze uživatelům ověřeným tokenem.

Zdrojový kód tohoto projektu lze nalézt na GitHubu.