Основна автентифікація HTTP за допомогою Node та Express 4


107

Схоже, реалізація базової HTTP-аутентифікації з Express v3 була тривіальною:

app.use(express.basicAuth('username', 'password'));

Версія 4 (я використовую 4.2) видалила basicAuthпроміжне програмне забезпечення, проте я трохи застряг. У мене є такий код, але він не змушує браузера спонукати користувача до облікових даних, що саме мені хотілося б (і те, що я думаю, що робив старий метод):

app.use(function(req, res, next) {
    var user = auth(req);

    if (user === undefined || user['name'] !== 'username' || user['pass'] !== 'password') {
        res.writeHead(401, 'Access invalid for user', {'Content-Type' : 'text/plain'});
        res.end('Invalid credentials');
    } else {
        next();
    }
});

2
Безсоромний штекер: я підтримую досить популярний модуль, який робить це просто і має найнеобхідніші функції, які вам знадобляться: express-basic-auth
— LionC

Нещодавно я відправив пакет @LionC, тому що мені довелося адаптувати його (даючи змогу авторизованим контекстам) за надто короткий проміжок часу для проекту компанії: npmjs.com/package/spresso-authy
— castarco

Відповіді:


108

Простий базовий Auth з ванільним JavaScript (ES6)

app.use((req, res, next) => {

  // -----------------------------------------------------------------------
  // authentication middleware

  const auth = {login: 'yourlogin', password: 'yourpassword'} // change this

  // parse login and password from headers
  const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
  const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':')

  // Verify login and password are set and correct
  if (login && password && login === auth.login && password === auth.password) {
    // Access granted...
    return next()
  }

  // Access denied...
  res.set('WWW-Authenticate', 'Basic realm="401"') // change this
  res.status(401).send('Authentication required.') // custom message

  // -----------------------------------------------------------------------

})

Примітка. Цей "проміжок" можна використовувати в будь-якому оброблювальному пристрої. Просто видаліть next()і скасуйте логіку. Дивіться приклад 1-твердження нижче або історію редагування цієї відповіді.

Чому?

  • req.headers.authorizationмістить значення " Basic <base64 string>", але воно також може бути порожнім, і ми не хочемо, щоб воно вийшло з ладу, отже, дивне комбо|| ''
  • Вузол не знає atob()і btoa(), отже,Buffer

ES6 -> ES5

constпросто var.. роду
(x, y) => {...}простоfunction(x, y) {...}
const [login, password] = ...split() це всього лише два varзавдання в одному

джерело натхнення (використовує пакети)


Наведене вище - надзвичайно простий приклад, який повинен був бути дуже коротким та швидко розгортатися на сервері вашого ігрового майданчика. Але як було зазначено в коментарях, паролі також можуть містити символи двокрапки :. Щоб правильно витягти його з b64auth , ви можете використовувати це.

  // parse login and password from headers
  const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
  const strauth = Buffer.from(b64auth, 'base64').toString()
  const splitIndex = strauth.indexOf(':')
  const login = strauth.substring(0, splitIndex)
  const password = strauth.substring(splitIndex + 1)

  // using shorter regex by @adabru
  // const [_, login, password] = strauth.match(/(.*?):(.*)/) || []

Основний auth в одному твердженні

... з іншого боку, якщо ви коли-небудь використовуєте одну або дуже мало вхідних даних, це мінімальний необхідний вам мінімум: (вам навіть зовсім не потрібно розбирати облікові дані)

function (req, res) {
//btoa('yourlogin:yourpassword') -> "eW91cmxvZ2luOnlvdXJwYXNzd29yZA=="
//btoa('otherlogin:otherpassword') -> "b3RoZXJsb2dpbjpvdGhlcnBhc3N3b3Jk"

  // Verify credentials
  if (  req.headers.authorization !== 'Basic eW91cmxvZ2luOnlvdXJwYXNzd29yZA=='
     && req.headers.authorization !== 'Basic b3RoZXJsb2dpbjpvdGhlcnBhc3N3b3Jk')        
    return res.status(401).send('Authentication required.') // Access denied.   

  // Access granted...
  res.send('hello world')
  // or call next() if you use it as middleware (as snippet #1)
}

PS: чи потрібно мати як "безпечний", так і "громадський" шлях? Подумайте про використання express.routerзамість цього.

var securedRoutes = require('express').Router()

securedRoutes.use(/* auth-middleware from above */)
securedRoutes.get('path1', /* ... */) 

app.use('/secure', securedRoutes)
app.get('public', /* ... */)

// example.com/public       // no-auth
// example.com/secure/path1 // requires auth

2
Найкраще з лота ... :)
— Анупам Басак

2
Не використовуйте, .split(':')оскільки він задихатиметься паролями, що містять принаймні одну двокрапку. Такі паролі дійсні згідно з RFC 2617 .
— Distortum

1
Ви також можете використовувати RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []для частини товстої кишки.
— адабр

3
Використання !==для порівняння паролів залишає вас вразливими до атак часу. en.wikipedia.org/wiki/Timing_attack переконайтесь, що ви використовуєте постійне порівняння рядків часу.
— hraban

1
Використання Buffer.from() // for stringsабо Buffer.alloc() // for numbersяк Buffer()застаріле через проблеми безпеки.
— Містер Чужор

71

TL; DR:

☒ express.basicAuthпішло
☒ basic-auth-connectзастаріло
☒ basic-authне має логіки
☒ http-authє надмірність
☑ express-basic-authце те, що ти хочеш

Більше інформації:

Оскільки ви використовуєте Express, то ви можете використовувати express-basic-authпроміжне програмне забезпечення.

Дивіться документи:

Приклад:

const app = require('express')();
const basicAuth = require('express-basic-auth');
 
app.use(basicAuth({
    users: { admin: 'supersecret123' },
    challenge: true // <--- needed to actually show the login dialog!
}));

17
Зайняв мене час, щоб розібратися у challenge: trueваріанті
— Віталій Цуріан

1
@VitaliiZurian Добре - я додав це до відповіді. Дякуємо, що вказали на це.
— rsp

4
@rsp Чи знаєте ви, як застосувати це лише до певних маршрутів?
— Хорхе Л Ернандес

Якщо ви не хочете додавати інші залежності, дуже просто написати основний auth вручну на одному рядку ...
— Qwerty

як виглядатиме URL-адреса клієнта?
— GGEv

57

Багато програмного забезпечення було витягнуто з ядра Express в v4 і розміщено в окремі модулі. Основний модуль аутентифікації тут: https://github.com/expressjs/basic-auth-connect

Ваш приклад потрібно просто змінити до цього:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));

19
Цей модуль претендує на застарілість (хоча альтернатива, яку він пропонує, здається незадовільною)
— Арнут Енгелен,

3
^^ абсолютно незадовільно, як у густо недокументованих. нульовий приклад використання як середнього програмного забезпечення, для чого це, мабуть, добре, але виклик недоступний. приклад, який вони дають, чудово підходить для загальності, але не для використання інформації.
— Вілі Кулик

Так, ця застаріла, і хоча рекомендований мало документів, код дуже простий github.com/jshttp/basic-auth/blob/master/index.js
— Loourr

1
Я описав, як користуватися basic-authбібліотекою у цій відповіді
— Loourr

Як існує весь модуль, заснований на тому, щоб ввести код у чіткий текст у коді ?? Принаймні затемнення цього порівняння в base64 здається дещо кращим.
— користувач1944491

33

Я використовував код для оригіналу, basicAuthщоб знайти відповідь:

app.use(function(req, res, next) {
    var user = auth(req);

    if (user === undefined || user['name'] !== 'username' || user['pass'] !== 'password') {
        res.statusCode = 401;
        res.setHeader('WWW-Authenticate', 'Basic realm="MyRealmName"');
        res.end('Unauthorized');
    } else {
        next();
    }
});

10
цей модуль вважається застарілим, замість цього використовуйте jshttp / basic-auth (той самий api, тому відповідь все ще застосовується)
— Michael

32

Я змінив у express 4.0 основну аутентифікацію з http-auth , код:

var auth = require('http-auth');

var basic = auth.basic({
        realm: "Web."
    }, function (username, password, callback) { // Custom authentication method.
        callback(username === "userName" && password === "password");
    }
);

app.get('/the_url', auth.connect(basic), routes.theRoute);

1
Це буквально plug and play. Чудовий.
— sidonaldson

20

Здається, для цього є кілька модулів, деякі - застарілі.

Це виглядає активно:
https://github.com/jshttp/basic-auth

Ось приклад використання:

// auth.js

var auth = require('basic-auth');

var admins = {
  'art@vandelay-ind.org': { password: 'pa$$w0rd!' },
};


module.exports = function(req, res, next) {

  var user = auth(req);
  if (!user || !admins[user.name] || admins[user.name].password !== user.pass) {
    res.set('WWW-Authenticate', 'Basic realm="example"');
    return res.status(401).send();
  }
  return next();
};




// app.js

var auth = require('./auth');
var express = require('express');

var app = express();

// ... some not authenticated middlewares

app.use(auth);

// ... some authenticated middlewares

Переконайтесь, що розміщуєте authпроміжне програмне забезпечення в потрібному місці, будь-яке проміжне програмне забезпечення до цього не буде автентифіковане.


Я насправді виступаю за "basic-auth-connect", ім'я - це погано, але функціональність мудріша - це краще, ніж "basic-auth". Все останнє - це заголовок авторизації розбору. Вам все одно доведеться складати implementпротокол (він же надсилає правильний заголовок)
— FDIM

Ідеально! Дякую за це. Це спрацювало і все добре пояснило.
— Tania Rascia

Я спробував це, але він просто просить мене ввійти через безперервний цикл.
— jdog

6

Ми можемо реалізувати базову авторизацію, не потребуючи жодного модуля

//1.
var http = require('http');

//2.
var credentials = {
    userName: "vikas kohli",
    password: "vikas123"
};
var realm = 'Basic Authentication';

//3.
function authenticationStatus(resp) {
    resp.writeHead(401, { 'WWW-Authenticate': 'Basic realm="' + realm + '"' });
    resp.end('Authorization is needed');

};

//4.
var server = http.createServer(function (request, response) {
    var authentication, loginInfo;

    //5.
    if (!request.headers.authorization) {
        authenticationStatus (response);
        return;
    }

    //6.
    authentication = request.headers.authorization.replace(/^Basic/, '');

    //7.
    authentication = (new Buffer(authentication, 'base64')).toString('utf8');

    //8.
    loginInfo = authentication.split(':');

    //9.
    if (loginInfo[0] === credentials.userName && loginInfo[1] === credentials.password) {
        response.end('Great You are Authenticated...');
         // now you call url by commenting the above line and pass the next() function
    }else{

    authenticationStatus (response);

}

});
 server.listen(5050);

Джерело: - http://www.dotnetcurry.com/nodejs/1231/basic-authentication-using-nodejs


1

Express видалив цю функціональність і тепер рекомендує використовувати бібліотеку basic-auth .

Ось приклад використання:

var http = require('http')
var auth = require('basic-auth')

// Create server
var server = http.createServer(function (req, res) {
  var credentials = auth(req)

  if (!credentials || credentials.name !== 'aladdin' || credentials.pass !== 'opensesame') {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="example"')
    res.end('Access denied')
  } else {
    res.end('Access granted')
  }
})

// Listen
server.listen(3000)

Щоб надіслати запит на цей маршрут, вам потрібно включити заголовок авторизації, відформатований для базового авт.

Перш ніж надіслати запит на згортання, слід взяти кодування base64name:pass або в цьому випадку aladdin:opensesameрівнеYWxhZGRpbjpvcGVuc2VzYW1l

Ваш запит на згортання буде виглядати так:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/

0
function auth (req, res, next) {
  console.log(req.headers);
  var authHeader = req.headers.authorization;
  if (!authHeader) {
      var err = new Error('You are not authenticated!');
      res.setHeader('WWW-Authenticate', 'Basic');
      err.status = 401;
      next(err);
      return;
  }
  var auth = new Buffer.from(authHeader.split(' ')[1], 'base64').toString().split(':');
  var user = auth[0];
  var pass = auth[1];
  if (user == 'admin' && pass == 'password') {
      next(); // authorized
  } else {
      var err = new Error('You are not authenticated!');
      res.setHeader('WWW-Authenticate', 'Basic');      
      err.status = 401;
      next(err);
  }
}
app.use(auth);

Сподіваюся, що це вирішить проблему, але, будь ласка, додайте до нього пояснення свого коду, щоб користувач отримав ідеальне розуміння, чого він / вона насправді хоче.
— Джайміл Патель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.