Базовая 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
Бесстыдный плагин: у меня есть довольно популярный модуль, который упрощает это и имеет большинство стандартных функций, которые вам могут понадобиться: 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() и измените логику в обратном порядке. См. Пример с одним утверждением ниже или историю изменений этого ответа.

Почему?

  • 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_timingsa. Посмотрите, например, как это реализовано в 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
Лучший ответ имо. Быстро и полезно - person Kurkov Igor; 16.02.2021

Большое количество промежуточного программного обеспечения было извлечено из ядра Express в версии 4 и помещено в отдельные модули. Базовый модуль аутентификации находится здесь: 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
Я описал, как использовать библиотеку 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

В экспресс-версии 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
Это буквально подключи и работай. Отлично. - 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