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.