Основна автентифікація 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.