Otentikasi HTTP dasar dengan Node dan Express 4

107

Sepertinya menerapkan otentikasi HTTP dasar dengan Express v3 itu sepele:

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

Versi 4 (saya menggunakan 4.2) menghapus basicAuthmiddleware, jadi saya sedikit buntu. Saya memiliki kode berikut, tetapi tidak menyebabkan browser meminta kredensial pengguna, yang saya inginkan (dan apa yang saya bayangkan dilakukan metode lama):

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();
    }
});
Dov
sumber
2
Steker tak tahu malu: Saya mempertahankan modul yang cukup populer yang membuatnya mudah dan memiliki sebagian besar fitur standar yang Anda perlukan: express-basic-auth
LionC
Saya baru-baru ini membuat fork paket @LionC karena saya harus menyesuaikannya (mengaktifkan pengotorisasi sadar konteks) dalam rentang waktu yang sangat singkat untuk proyek perusahaan: npmjs.com/package/spresso-authy
castarco

Jawaban:

108

Simple Basic Auth dengan 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

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

})

catatan: "Middleware" ini dapat digunakan di semua handler. Hapus saja next()dan balikkan logikanya. Lihat contoh 1 pernyataan di bawah ini, atau riwayat edit jawaban ini.

Mengapa?

  • req.headers.authorizationberisi nilai " Basic <base64 string>", tetapi juga bisa kosong dan kami tidak ingin gagal, karenanya kombo aneh dari|| ''
  • Node tidak tahu atob()dan btoa(), karenanyaBuffer

ES6 -> ES5

consthanya var.. semacam
(x, y) => {...}ini hanya function(x, y) {...}
const [login, password] = ...split()hanya duavar tugas dalam satu

sumber inspirasi (menggunakan paket)


Di atas adalah contoh super sederhana yang dimaksudkan agar sangat pendek dan dapat diterapkan dengan cepat ke server taman bermain Anda. Tetapi seperti yang ditunjukkan di komentar, kata sandi juga dapat berisi karakter titik dua :. Untuk mengekstraknya dengan benar dari b64auth , Anda dapat menggunakan ini.

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

Otorisasi dasar dalam satu pernyataan

... di sisi lain, jika Anda hanya pernah menggunakan satu atau sangat sedikit login, ini adalah kebutuhan minimum yang Anda butuhkan: (Anda bahkan tidak perlu mengurai kredensial sama sekali)

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

NB: apakah Anda perlu memiliki jalur "aman" dan "publik"? Pertimbangkan untuk menggunakan express.routersebagai gantinya.

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
Qwerty
sumber
2
Terbaik dari semuanya ... :)
Anupam Basak
2
Jangan gunakan .split(':')karena akan mencekik sandi yang mengandung setidaknya satu titik dua. Kata sandi tersebut valid menurut RFC 2617 .
Distortum
1
Anda juga dapat menggunakan RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []untuk bagian titik dua.
adabru
3
Menggunakan !==untuk membandingkan kata sandi membuat Anda rentan terhadap serangan waktu. en.wikipedia.org/wiki/Timing_attack pastikan Anda menggunakan perbandingan string waktu yang konstan.
hraban
1
Gunakan Buffer.from() // for stringsatau Buffer.alloc() // for numbersseperti yang Buffer()tidak digunakan lagi karena masalah keamanan.
Tn. Alien
71

TL; DR:

express.basicAuthhilang
basic-auth-connecttidak digunakan lagi
basic-authtidak memiliki logika
http-authberlebihan
express-basic-auth adalah yang Anda inginkan

Info lebih lanjut:

Karena Anda menggunakan Express, maka Anda dapat menggunakan express-basic-auth middleware.

Lihat dokumennya:

Contoh:

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!
}));
rsp
sumber
17
Butuh beberapa saat untuk mencari tahu tentang challenge: trueopsi
Vitalii Zurian
1
@VitaliiZurian Poin yang bagus - Saya menambahkannya ke jawaban. Terima kasih telah menunjukkannya.
rsp
4
@rsp Apakah Anda tahu bagaimana menerapkan ini hanya untuk rute tertentu?
Jorge L Hernandez
Jika Anda tidak ingin menambahkan dependensi lain, sangat mudah untuk menulis autentikasi dasar dengan tangan di satu baris ...
Qwerty
Bagaimana tampilan url klien?
GGEv
57

Banyak middleware ditarik keluar dari inti Express di v4, dan dimasukkan ke modul terpisah. Modul autentikasi dasar ada di sini: https://github.com/expressjs/basic-auth-connect

Contoh Anda hanya perlu diubah menjadi ini:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
Brian Prodoehl
sumber
19
Modul ini mengklaim tidak digunakan lagi (meskipun alternatif yang disarankan tampaknya tidak memuaskan)
Arnout Engelen
3
^^ benar-benar tidak memuaskan seperti yang tidak terdokumentasi dengan padat. nol contoh penggunaan sebagai middleware, yang mungkin bagus untuk itu, tetapi pemanggilan tidak tersedia. contoh yang mereka berikan sangat bagus untuk umum, tetapi tidak untuk info penggunaan.
Wylie Kulik
Ya yang ini sudah usang, dan sementara yang direkomendasikan rendah pada dokumen, kodenya sangat sederhana github.com/jshttp/basic-auth/blob/master/index.js
Loourr
1
Saya telah menjelaskan cara menggunakan basic-authperpustakaan dalam jawaban ini
Loourr
Bagaimana seluruh modul ada berdasarkan penempatan kata sandi dalam teks yang jelas dalam kode ?? Setidaknya mengaburkannya dengan membandingkan di base64 tampaknya sedikit lebih baik.
pengguna1944491
33

Saya menggunakan kode untuk aslinya basicAuthuntuk menemukan jawabannya:

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();
    }
});
Dov
sumber
10
modul ini dianggap usang, gunakan jshttp / basic-auth sebagai gantinya (api yang sama jadi jawaban masih berlaku)
Michael
32

Saya mengubah express 4.0 otentikasi dasar dengan http-auth , kodenya adalah:

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);
WarsClon
sumber
1
Ini benar-benar plug and play. Sangat bagus.
sidonalds pada
20

Tampaknya ada beberapa modul untuk melakukannya, beberapa tidak digunakan lagi.

Yang ini terlihat aktif:
https://github.com/jshttp/basic-auth

Berikut adalah contoh penggunaan:

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

Pastikan Anda meletakkan authmiddleware di tempat yang benar, middleware apa pun sebelumnya tidak akan diautentikasi.

Michael
sumber
Saya sebenarnya mendukung 'basic-auth-connect', nama itu buruk tetapi secara fungsionalitas itu lebih baik daripada 'basic-auth'. Semua yang terakhir dilakukan, adalah header otorisasi parse. Anda masih harus implementmenggunakan protokolnya sendiri (alias mengirim header yang benar)
FDIM
Sempurna! Terima kasih untuk ini. Ini berhasil dan menjelaskan semuanya dengan baik.
Tania Rascia
Saya mencoba ini, tetapi terus meminta saya untuk masuk melalui loop berkelanjutan.
jdog
6

Kami dapat menerapkan otorisasi dasar tanpa memerlukan modul apa pun

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

Sumber: - http://www.dotnetcurry.com/nodejs/1231/basic-authentication-using-nodejs

VIKAS KOHLI
sumber
1

Express telah menghapus fungsionalitas ini dan sekarang merekomendasikan Anda menggunakan pustaka autentikasi dasar .

Berikut contoh cara menggunakan:

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)

Untuk mengirim permintaan ke rute ini, Anda perlu menyertakan header Otorisasi yang diformat untuk autentikasi dasar.

Mengirim permintaan curl terlebih dahulu, Anda harus mengambil pengkodean base64name:pass atau dalam hal aladdin:opensesameini sama denganYWxhZGRpbjpvcGVuc2VzYW1l

Permintaan curl Anda akan terlihat seperti:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
Loourr
sumber
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);
subair adams ohikere
sumber
Semoga Ini akan menyelesaikan masalah tetapi tolong tambahkan penjelasan kode Anda dengannya sehingga pengguna akan mendapatkan pemahaman yang sempurna yang dia benar-benar inginkan.
Jaimil Patel