การพิสูจน์ตัวตน HTTP พื้นฐานด้วย Node และ Express 4


111

ดูเหมือนว่าการใช้การตรวจสอบสิทธิ์ 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

คำตอบ:


111

การตรวจสอบสิทธิ์พื้นฐานอย่างง่ายด้วย vanilla 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(/(.*?):(.*)/) || []

การตรวจสอบสิทธิ์พื้นฐานในคำสั่งเดียว

... ในทางกลับกันหากคุณเคยใช้การเข้าสู่ระบบเพียงครั้งเดียวหรือน้อยมากนี่คือขั้นต่ำที่คุณต้องการ: (คุณไม่จำเป็นต้องแยกวิเคราะห์ข้อมูลรับรองเลย)

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 stringsหรือBuffer.alloc() // for numbersตามที่Buffer()เลิกใช้เนื่องจากปัญหาด้านความปลอดภัย
Mr. Alien

74

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

18
ใช้เวลาสักพักเพื่อหาข้อมูลเกี่ยวกับchallenge: trueตัวเลือก
Vitalii Zurian

1
@VitaliiZurian จุดดี - ฉันเพิ่มคำตอบแล้ว ขอบคุณที่ชี้ให้เห็น
rsp

4
@rsp คุณรู้วิธีใช้สิ่งนี้กับเส้นทางที่เจาะจงเท่านั้นหรือไม่?
Jorge L Hernandez

หากคุณไม่ต้องการเพิ่มการอ้างอิงอื่น ๆ มันง่ายมากที่จะเขียนการรับรองความถูกต้องพื้นฐานด้วยมือในบรรทัดเดียว ...
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
โมดูลนี้อ้างว่าเลิกใช้งานแล้ว (แม้ว่าทางเลือกที่แนะนำจะดูเหมือนไม่น่าพอใจ)
Arnout Engelen

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ใช้โปรโตคอลด้วยตัวเอง (aka ส่งส่วนหัวที่ถูกต้อง)
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 ได้ลบฟังก์ชันนี้ออกและตอนนี้แนะนำให้คุณใช้ไลบรารีการตรวจสอบสิทธิ์พื้นฐาน

นี่คือตัวอย่างวิธีใช้:

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 ก่อนอื่นคุณต้องใช้การเข้ารหัสbase64name: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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.