Autenticazione HTTP di base con Node ed Express 4


107

Sembra che l'implementazione dell'autenticazione HTTP di base con Express v3 sia stata banale:

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

La versione 4 (sto usando 4.2) ha rimosso il basicAuthmiddleware, quindi sono un po 'bloccato. Ho il seguente codice, ma non fa sì che il browser richieda all'utente le credenziali, che è quello che vorrei (e quello che immagino abbia fatto il vecchio metodo):

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
Spina senza vergogna: mantengo un modulo abbastanza popolare che lo rende facile e ha la maggior parte delle funzionalità standard di cui avresti bisogno: express-basic-auth
LionC

Recentemente ho biforcato il pacchetto di @LionC perché ho dovuto adattarlo (abilitando gli autorizzatori sensibili al contesto) in un brevissimo lasso di tempo per un progetto aziendale: npmjs.com/package/spresso-authy
castarco

Risposte:


108

Autenticazione di base semplice con JavaScript vanilla (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

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

})

nota: questo "middleware" può essere utilizzato in qualsiasi gestore. Basta rimuovere next()e invertire la logica. Vedi l' esempio di una dichiarazione di seguito o la cronologia delle modifiche di questa risposta.

Perché?

  • req.headers.authorizationcontiene il valore " Basic <base64 string>", ma può anche essere vuoto e non vogliamo che fallisca, da qui la strana combinazione di|| ''
  • Node non lo sa atob()e btoa(), quindi, il fileBuffer

ES6 -> ES5

constè solo var.. specie di
(x, y) => {...}è solo function(x, y) {...}
const [login, password] = ...split()è a soli due varincarichi in uno

fonte di ispirazione (utilizza pacchetti)


Quello sopra è un esempio semplicissimo che doveva essere molto breve e rapidamente distribuibile sul tuo server playground. Ma come è stato sottolineato nei commenti, le password possono contenere anche i due punti :. Per estrarlo correttamente da b64auth , puoi usare questo.

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

Autenticazione di base in un'unica istruzione

... d'altra parte, se usi solo uno o pochissimi accessi, questo è il minimo indispensabile di cui hai bisogno: (non hai nemmeno bisogno di analizzare le credenziali)

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: devi avere percorsi sia "sicuri" che "pubblici"? Considera l'utilizzo express.routerinvece.

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
Il migliore di tutti ... :)
Anupam Basak

2
Non utilizzare .split(':')perché si strozzerà con le password contenenti almeno un due punti. Tali password sono valide secondo RFC 2617 .
Distortum

1
Puoi anche usare RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []per la parte dei due punti.
adabru

3
L'utilizzo !==per confrontare le password ti rende vulnerabile agli attacchi temporali. en.wikipedia.org/wiki/Timing_attack assicurati di utilizzare un confronto tra stringhe di tempo costante.
hraban

1
Usa Buffer.from() // for stringso Buffer.alloc() // for numberscome Buffer()è deprecato a causa di problemi di sicurezza.
Mr. Alien

71

TL; DR:

express.basicAuthè andato
basic-auth-connectè deprecato
basic-authnon ha alcuna logica
http-authè eccessivo
express-basic-authè quello che vuoi

Ulteriori informazioni:

Poiché stai utilizzando Express, puoi utilizzare il express-basic-authmiddleware.

Vedi i documenti:

Esempio:

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
Mi ci è challenge: true
voluto

1
@VitaliiZurian Buon punto - l'ho aggiunto alla risposta. Grazie per segnalarlo.
rsp

4
@rsp Sai come applicarlo solo a percorsi specifici?
Jorge L Hernandez

Se non vuoi aggiungere altre dipendenze, è molto facile scrivere l'autenticazione di base a mano su una riga ...
Qwerty

come sarebbe l'URL del client?
GGEv

57

Gran parte del middleware è stata estratta dal core Express nella v4 e inserita in moduli separati. Il modulo di autenticazione di base è qui: https://github.com/expressjs/basic-auth-connect

Il tuo esempio dovrebbe solo cambiare in questo:

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

19
Questo modulo afferma di essere deprecato (anche se l'alternativa che suggerisce sembra insoddisfacente)
Arnout Engelen

3
^^ assolutamente insoddisfacente in quanto densamente priva di documenti. nessun esempio di utilizzo come middleware, che probabilmente è utile, ma l'invocazione non è disponibile. l'esempio che danno è ottimo per la generalità, ma non per le informazioni sull'utilizzo.
Wylie Kulik

Sì, questo è deprecato e, sebbene quello consigliato sia a
corto di

1
Ho descritto come utilizzare la basic-authlibreria in questa risposta
Loourr

Come esiste un intero modulo basato sull'inserimento della password in chiaro nel codice ? Almeno oscurarlo confrontando in base64 sembra leggermente migliore.
user1944491

33

Ho usato il codice dell'originale basicAuthper trovare la risposta:

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
questo modulo è considerato deprecato, usa invece jshttp / basic-auth (stessa API quindi la risposta si applica ancora)
Michael

32

Ho modificato in express 4.0 l'autenticazione di base con http-auth , il codice è:

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
Questo è letteralmente plug and play. Eccellente.
Sidonaldson

20

Sembra che ci siano più moduli per farlo, alcuni sono deprecati.

Questo sembra attivo:
https://github.com/jshttp/basic-auth

Ecco un esempio di utilizzo:

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

Assicurati di mettere il authmiddleware nella posizione corretta, qualsiasi middleware precedente non verrà autenticato.


In realtà sono a favore di "basic-auth-connect", il nome è pessimo ma per quanto riguarda la funzionalità è migliore di "basic-auth". Tutto ciò che fa è analizzare l'intestazione dell'autorizzazione. Devi ancora utilizzare implementil protocollo (ovvero inviare l'intestazione corretta)
FDIM

Perfetto! Grazie per questo. Ha funzionato e ha spiegato tutto bene.
Tania Rascia

Ho provato questo, ma continua a chiedermi di accedere tramite un ciclo continuo.
jdog

6

Possiamo implementare l'autorizzazione di base senza bisogno di alcun modulo

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

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


1

Express ha rimosso questa funzionalità e ora consiglia di utilizzare la libreria di autenticazione di base .

Ecco un esempio di come utilizzare:

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)

Per inviare una richiesta a questa route è necessario includere un'intestazione di autorizzazione formattata per l'autenticazione di base.

Inviando una richiesta di curl prima devi prendere la codifica base64 di name:passo in questo caso aladdin:opensesameche è uguale aYWxhZGRpbjpvcGVuc2VzYW1l

La tua richiesta di ricciolo sarà quindi simile a:

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

Spero che risolva il problema, ma per favore aggiungi una spiegazione del tuo codice con esso in modo che l'utente possa capire perfettamente cosa vuole veramente.
Jaimil Patel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.