Xác thực HTTP cơ bản với Node và Express 4


107

Có vẻ như việc triển khai xác thực HTTP cơ bản với Express v3 là không bình thường:

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

Tuy nhiên, phiên bản 4 (tôi đang sử dụng 4.2) đã loại bỏ basicAuthphần mềm trung gian, vì vậy tôi hơi mắc kẹt. Tôi có mã sau, nhưng nó không khiến trình duyệt nhắc người dùng nhập thông tin đăng nhập, đó là những gì tôi muốn (và những gì tôi tưởng tượng phương pháp cũ đã làm):

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
Shameless plug: Tôi duy trì một mô-đun khá phổ biến giúp việc đó trở nên dễ dàng và có hầu hết các tính năng tiêu chuẩn mà bạn cần: express-basic-auth
LionC

Gần đây tôi chia hai gói @LionC là bởi vì tôi đã phải thích ứng với nó (cho phép ủy quyền nhận biết ngữ cảnh) trong một khoảng siêu ngắn thời gian cho một dự án công ty: npmjs.com/package/spresso-authy
castarco

Câu trả lời:


108

Auth cơ bản đơn giản với JavaScript vani (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

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

})

lưu ý: "Phần mềm trung gian" này có thể được sử dụng trong bất kỳ trình xử lý nào. Chỉ cần loại bỏ next()và đảo ngược logic. Xem ví dụ 1 câu bên dưới hoặc lịch sử chỉnh sửa của câu trả lời này.

Tại sao?

  • req.headers.authorizationchứa giá trị " Basic <base64 string>", nhưng nó cũng có thể trống và chúng tôi không muốn nó bị lỗi, do đó, kết hợp kỳ lạ của|| ''
  • Node không biết atob()btoa()do đóBuffer

ES6 -> ES5

constchỉ là var.. đại loại
(x, y) => {...}là chỉ function(x, y) {...}
const [login, password] = ...split()là hai varnhiệm vụ trong một

nguồn cảm hứng (sử dụng các gói)


Trên đây là một ví dụ siêu đơn giản nhằm mục đích siêu ngắn và có thể triển khai nhanh chóng cho máy chủ sân chơi của bạn. Nhưng như đã được chỉ ra trong các nhận xét, mật khẩu cũng có thể chứa các ký tự dấu hai chấm :. Để giải nén chính xác nó từ b64auth , bạn có thể sử dụng điều này.

  // 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(/(.*?):(.*)/) || []

Xác thực cơ bản trong một câu lệnh

... mặt khác, nếu bạn chỉ sử dụng một hoặc rất ít thông tin đăng nhập, đây là mức tối thiểu bạn cần: (bạn thậm chí không cần phải phân tích cú pháp thông tin đăng nhập)

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)
}

Tái bút: bạn có cần phải có cả hai đường dẫn "an toàn" và "công khai" không? Cân nhắc sử dụng express.routerthay thế.

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
Tốt nhất trong số rất nhiều ... :)
Anupam Basak

2
Đừng sử dụng .split(':')vì nó sẽ làm nghẹt mật khẩu có ít nhất một dấu hai chấm. Mật khẩu như vậy hợp lệ theo RFC 2617 .
Distortum

1
Bạn cũng có thể sử dụng RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []cho phần dấu hai chấm.
adabru

3
Việc sử dụng !==để so sánh mật khẩu khiến bạn dễ bị tấn công theo thời gian. vi.wikipedia.org/wiki/Timing_attack đảm bảo bạn sử dụng so sánh chuỗi thời gian không đổi.
hraban

1
Sử dụng Buffer.from() // for stringshoặc Buffer.alloc() // for numbersnhư Buffer()không được dùng nữa do các vấn đề bảo mật.
Mr. Alien

71

TL; DR:

express.basicAuthđã biến mất
basic-auth-connectkhông dùng nữa
basic-authkhông có bất kỳ logic nào
http-authlà quá mức cần thiết
express-basic-authlà những gì bạn muốn

Thêm thông tin:

Vì bạn đang sử dụng Express nên bạn có thể sử dụng express-basic-authphần mềm trung gian.

Xem tài liệu:

Thí dụ:

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
Đã cho tôi một thời gian để tìm hiểu về các challenge: truetùy chọn
Vitalii Zurian

1
@VitaliiZurian Điểm hay - Tôi đã thêm nó vào câu trả lời. Cảm ơn đã chỉ ra điều đó.
rsp

4
@rsp Bạn có biết cách áp dụng điều này chỉ cho các tuyến đường cụ thể không?
Jorge L Hernandez,

Nếu bạn không muốn thêm phụ thuộc khác, nó rất dễ dàng để viết auth cơ bản bằng tay trên cùng một dòng ...
Qwerty

url của khách hàng trông như thế nào?
GGEv

57

Rất nhiều phần mềm trung gian đã được rút ra khỏi lõi Express trong v4 và được đưa vào các mô-đun riêng biệt. Mô-đun auth cơ bản ở đây: https://github.com/expressjs/basic-auth-connect

Ví dụ của bạn sẽ chỉ cần thay đổi thành:

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

19
Module này tuyên bố sẽ phản đối (mặc dù thay thế nó cho thấy dường như không thỏa mãn)
Arnout Engelen

3
^^ hoàn toàn không hài lòng như trong không có giấy tờ dày đặc. không có ví dụ nào về việc sử dụng làm phần mềm trung gian, mà nó có thể tốt cho nó, nhưng lời gọi không khả dụng. ví dụ mà họ đưa ra là rất tốt cho tính tổng quát, nhưng không tốt cho thông tin sử dụng.
Wylie Kulik

Ừ cái này bị phản đối, và trong khi một đề nghị là thấp trên tài liệu, mã này là rất đơn giản github.com/jshttp/basic-auth/blob/master/index.js
Loourr

1
Tôi đã mô tả làm thế nào để sử dụng basic-auththư viện trong câu trả lời này
Loourr

Làm thế nào để toàn bộ mô-đun tồn tại dựa trên việc đặt mật khẩu dưới dạng văn bản rõ ràng trong mã ?? Ít nhất làm mờ nó bằng cách so sánh trong base64 có vẻ tốt hơn một chút.
user1944491

33

Tôi đã sử dụng mã cho bản gốc basicAuthđể tìm câu trả lời:

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
module này được coi là bị phản đối, sử dụng jshttp / cơ bản-auth thay (giống api để câu trả lời vẫn được áp dụng)
Michael

32

Tôi đã thay đổi trong express 4.0 xác thực cơ bản với http-auth , mã là:

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
Đây thực sự là plug and play. Tuyệt vời.
sidonaldson

20

Có vẻ như có nhiều mô-đun để làm điều đó, một số mô-đun không được dùng nữa.

Cái này có vẻ đang hoạt động:
https://github.com/jshttp/basic-auth

Đây là một ví dụ sử dụng:

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

Đảm bảo rằng bạn đặt authphần mềm trung gian vào đúng vị trí, bất kỳ phần mềm trung gian nào trước đó sẽ không được xác thực.


Tôi thực sự ủng hộ 'basic-auth-connect', tên thì tệ nhưng chức năng thì nó tốt hơn 'basic-auth'. Tất cả những gì sau này, là phân tích cú pháp tiêu đề ủy quyền. Bạn vẫn phải implementtự mình thực hiện giao thức (hay còn gọi là gửi tiêu đề chính xác)
FDIM

Hoàn hảo! Cảm ơn vì điều này. Điều này đã hoạt động và giải thích mọi thứ một cách độc đáo.
Tania Rascia

Tôi đã thử điều này, nhưng nó vẫn tiếp tục yêu cầu tôi đăng nhập qua vòng lặp liên tục.
jdog

6

Chúng tôi có thể triển khai ủy quyền cơ bản mà không cần bất kỳ mô-đun nào

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

Nguồn: - http://www.dotnetcurry.com/nodejs/1231/basic-authentication-using-nodejs


1

Express đã loại bỏ chức năng này và giờ đây khuyên bạn nên sử dụng thư viện basic-auth .

Đây là một ví dụ về cách sử dụng:

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)

Để gửi yêu cầu tới tuyến đường này, bạn cần bao gồm tiêu đề Ủy quyền được định dạng cho xác thực cơ bản.

Gửi yêu cầu curl trước tiên bạn phải sử dụng mã hóa base64 của name:passhoặc trong trường hợp aladdin:opensesamenày là mã hóaYWxhZGRpbjpvcGVuc2VzYW1l

Yêu cầu uốn tóc của bạn sau đó sẽ giống như sau:

 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);

Hy vọng nó sẽ giải quyết vấn đề nhưng vui lòng thêm giải thích về mã của bạn với nó để người dùng sẽ hiểu hoàn hảo mà họ thực sự muốn.
Jaimil Patel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.