使用Node and Express 4的基本HTTP身份验证


107

使用Express v3实施基本的HTTP身份验证似乎很简单:

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

Answers:


108

使用香草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()and 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(/(.*?):(.*)/) || []

一条语句中的基本身份验证

...另一方面,如果您仅使用一次或很少的登录,这是您所需要的最低要求:(您甚至根本不需要解析凭据)

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
最好的了……:)
Anupam Basak

2
请勿使用,.split(':')因为它会阻塞至少包含一个冒号的密码。此类密码根据RFC 2617有效。
Distortum

1
您也可以将RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []用于冒号部分。
adabru

3
使用!==比较密码会使您容易受到定时攻击。en.wikipedia.org/wiki/Timing_attack确保使用恒定时间字符串比较。
hraban

1
由于安全问题,不推荐使用Buffer.from() // for stringsBuffer.alloc() // for numbersas Buffer()
Alien先生

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选项
Vitalii Zurian

1
@VitaliiZurian好点-我将其添加到了答案中。感谢您指出。
rsp

4
@rsp您知道如何仅将其应用于特定路线吗?
豪尔赫·埃尔南德斯

如果您不想添加其他依赖项,那么很容易在一行上手工编写基本身份验证...
Qwerty

客户端网址看起来如何?
GGEv

57

很多中间件都从v4的Express核心中抽出,并放入单独的模块中。基本的身份验证模块在这里:https : //github.com/expressjs/basic-auth-connect

您的示例只需更改为:

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

19
该模块声称已被弃用(尽管它建议的替代方案似乎并不令人满意)
Arnout Engelen 2014年

3
^绝对令人不满意,就像在大量未记录的文件中一样。用作中间件的示例为零,这可能是有好处的,但是调用不可用。他们提供的示例非常适合一般性,但不适用于使用情况信息。
Wylie Kulik

是的,不建议使用该工具,尽管推荐的工具在文档中比较少,但是代码非常简单github.com/jshttp/basic-auth/blob/master/index.js
Loourr

1
我已经basic-auth此答案中
Loourr

基于将密码以明文形式输入代码,整个模块如何存在?至少通过在base64中进行比较来掩盖它似乎要好一些。
user1944491

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
这实际上是即插即用。优秀
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自己
填写

完善!这次真是万分感谢。这工作并很好地解释了一切。
塔尼亚·拉斯西亚

我试过了,但是一直在问我要通过连续循环登录。
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)

要将请求发送到此路由,您需要包括为基本身份验证设置格式的授权标头

首先发送curl请求,您必须采用的base64编码,name:pass或者在这种情况下aladdin:opensesame等于YWxhZGRpbjpvcGVuc2VzYW1l

您的curl请求将如下所示:

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

希望它将解决问题,但请在其中添加代码说明,以便用户完全理解他/她真正想要的。
Jaimil Patel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.