Основно HTTP удостоверяване с Node и Express 4

Изглежда, че прилагането на основно 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();
    }
});

person Dov    schedule 12.05.2014    source източник
comment
Shameless plug: Поддържам доста популярен модул, който прави това лесно и има повечето стандартни функции, от които се нуждаете: express-basic-auth   -  person LionC    schedule 09.02.2018
comment
Наскоро разклоних пакета на @LionC, защото трябваше да го адаптирам (разрешаване на авторизатори, съобразени с контекста) за изключително кратък период от време за фирмен проект: npmjs.com/package/spresso-authy   -  person castarco    schedule 08.07.2019
comment
@LionC не е ясно от документите на express-basic-auth как да го приложите само към един маршрут.   -  person Oleg Abrazhaev    schedule 14.07.2021


Отговори (9)


Обикновено основно удостоверяване с ванилен 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 .. нещо като< br> (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(/(.*?):(.*)/) || []

Основно удостоверяване в едно изявление

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

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
person Qwerty    schedule 24.11.2015
comment
Най-доброто от всички... :) - person Anupam Basak; 10.01.2016
comment
Наистина хубав скрипт за "удостоверяване" :) - person Peter Gordon; 14.04.2016
comment
@pgmann Можеше да го поправиш. Засрами се. коментари - person Qwerty; 09.05.2017
comment
Съжалявам, имам под 2000 представителя. Не мога да изпратя незначителни редакции за преглед (ще бъдат отхвърлени). Мислех, че ще го поправите, когато видяхте коментара, и наистина смятам, че сценарият е наистина хубав, поради което гласувах за него. - person Peter Gordon; 09.05.2017
comment
Не използвайте .split(':'), защото ще се задуши от пароли, съдържащи поне едно двоеточие. Такива пароли са валидни съгласно RFC 2617. - person Distortum; 04.02.2018
comment
Можете също да използвате RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || [] за частта с двоеточие. - person adabru; 04.08.2018
comment
Използването на !== за сравняване на пароли ви прави уязвими за атаки за определяне на времето. en.wikipedia.org/wiki/Timing_attack уверете се, че използвате сравнение на низове с постоянно време. - person hraban; 01.03.2019
comment
Използвайте Buffer.from() // for strings или Buffer.alloc() // for numbers, тъй като Buffer() е отхвърлено поради проблеми със сигурността. - person Mr. Alien; 25.02.2020
comment
Гласува против поради уязвимостта на тайминг атаката. Това трябва да използва nodejs.org/api/crypto.html#crypto_crypto_timingsafeequal_a_b вместо това. Вижте например как express-basic-auth внедри това, github.com/LionC/express-basic-auth/blob/. - person Thibaud Colas; 19.04.2020
comment
@ThibaudColas Можете ли, моля, да дадете връзка към статия, която другите да видят, обяснявайки защо това е проблем на отдалечен http сървър, където наред с други аспекти, например, времето за отговор въвежда недетерминирано забавяне във времето, което според моето мирско мнение замъглява тази уязвимост? - person Qwerty; 20.04.2020
comment
@Qwerty Нямам нито една добра статия по темата (Уикипедия помага ли? вижте en.wikipedia. org/wiki/Timing_attack споделено от @hraban). Вашето мнение е на място – мрежовите фактори правят атаките за определяне на времето по-малко практични. Но разчитането на това би било случай на сигурност чрез неизвестност – може да помогне тук и там, но не е толкова добра защита, колкото прилагането на сравнение в постоянно време. Вижте nvd.nist.gov/vuln/detail/CVE-2015-7576 за подобна уязвимост в Rails и как се оценява нейната сериозност. - person Thibaud Colas; 20.04.2020

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!
}));
person rsp    schedule 01.03.2018
comment
Отне ми известно време да разбера за challenge: true опция - person Vitalii Zurian; 27.03.2018
comment
@VitaliiZurian Добра точка - добавих го към отговора. Благодаря, че го посочи. - person rsp; 28.03.2018
comment
@rsp Знаете ли как да приложите това само към конкретни маршрути? - person Jorge L Hernandez; 13.05.2018
comment
Ако не искате да добавяте други зависимости, много е лесно да напишете основно удостоверяване на ръка на един ред... - person Qwerty; 24.04.2019
comment
как би изглеждал URL адресът на клиента? - person GGEv; 07.11.2019
comment
Този отговор беше решението на проблема, който търсех, но той странно промени поведението на статичните файлове, които сега имат предимство пред маршрутите, трябваше да изтрия index.html, за да работи това по някаква причина. Възелът е странен! - person Felipe Valdes; 14.09.2020
comment
Колко безопасно би било това за защита на моето приложение Heroku, което съм внедрил за лична употреба? В миналото съм бил жертва на откраднати и използвани мои AWS ключове, така че питам тук като предпазна мярка, тъй като личното ми приложение съдържа чувствителна информация. - person philosopher; 11.11.2020
comment
Най-добрият отговор imo. Бързо и полезно - person Kurkov Igor; 16.02.2021

Голяма част от междинния софтуер беше изваден от Express ядрото във v4 и поставен в отделни модули. Основният модул за удостоверяване е тук: https://github.com/expressjs/basic-auth-connect

Вашият пример просто трябва да се промени на това:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
person Brian Prodoehl    schedule 25.06.2014
comment
Този модул твърди, че е остарял (въпреки че алтернативата, която предлага, изглежда незадоволителна) - person Arnout Engelen; 01.08.2014
comment
^^ абсолютно незадоволително като в гъсто недокументирани. нулев пример за използване като мидълуер, за което вероятно е добър, но извикването е недостъпно. примерът, който дават, е страхотен за обобщение, но не и за информация за употреба. - person Wylie Kulik; 05.10.2016
comment
Да, този е остарял и докато препоръчаният е с малко документи, кодът е много прост https://github.com/jshttp/basic-auth/blob/master/index.js - person Loourr; 27.10.2017
comment
Описах как да използвам библиотеката basic-auth в този отговор - person Loourr; 27.10.2017
comment
Как съществува цял модул, базиран на поставяне на паролата в ясен текст в кода?? Поне прикриването му чрез сравняване в base64 изглежда малко по-добро. - person user1944491; 02.10.2019

Използвах кода за оригиналния 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();
    }
});
person Dov    schedule 12.05.2014
comment
този модул се счита за остарял, вместо това използвайте https://github.com/jshttp/basic-auth (същият API, така че отговорът все още е приложим ) - person Michael; 01.01.2015

Промених в express 4.0 основното удостоверяване с https://github.com/gevorg/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);
person WarsClon    schedule 08.07.2014
comment
Това е буквално plug and play. Отлично. - person sidonaldson; 04.12.2017

Изглежда има множество модули за това, някои са отхвърлени.

Този изглежда активен:
https://github.com/jshttp/basic-auth

Ето пример за използване:

// auth.js

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

var admins = {
  '[email protected]': { 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 мидълуер на правилното място, всеки мидълуер преди това няма да бъде удостоверен.

person Michael    schedule 01.01.2015
comment
Всъщност подкрепям „basic-auth-connect“, името е лошо, но по отношение на функционалността е по-добро от „basic-auth“. Всичко, което последното прави, е анализ на заглавката за оторизация. Все още трябва сами да implement протокола (известен още като изпращане на правилна заглавка) - person FDIM; 22.04.2015
comment
Перфектно! Благодаря ви за това. Това проработи и обясни всичко добре. - person Tania Rascia; 13.11.2017
comment
Опитах това, но продължава да ме моли да вляза чрез непрекъснат цикъл. - person jdog; 13.02.2018

Можем да внедрим основната авторизация, без да се нуждаем от модул

//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

person VIKAS KOHLI    schedule 18.12.2017

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)

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

Изпращайки curl заявка, първо трябва да вземете base64 кодиране на name:pass или в този случай aladdin:opensesame, което е равно на YWxhZGRpbjpvcGVuc2VzYW1l

Вашата заявка за къдрици ще изглежда така:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
person Loourr    schedule 27.10.2017

person    schedule
comment
Надяваме се, че ще реши проблема, но моля, добавете обяснение на вашия код с него, така че потребителят да разбере перфектно какво той/тя наистина иска. - person Jaimil Patel; 01.06.2020