Podstawowe uwierzytelnianie HTTP za pomocą Node i Express 4

107

Wygląda na to, że implementacja podstawowego uwierzytelniania HTTP w Express v3 była banalna:

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

Wersja 4 (używam 4.2) usunęła jednak basicAuthoprogramowanie pośredniczące, więc trochę utknąłem. Mam następujący kod, ale nie powoduje to, że przeglądarka monituje użytkownika o poświadczenia, co chciałbym (i wyobrażam sobie, że zrobiła to stara metoda):

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
źródło
2
Bezwstydna wtyczka: utrzymuję dość popularny moduł, który to ułatwia i ma większość standardowych funkcji, których potrzebujesz: express-basic-auth
LionC
Niedawno rozwidliłem pakiet @LionC, ponieważ musiałem go zaadaptować (włączając kontekstowe autoryzatory) w bardzo krótkim czasie dla projektu firmowego
castarco

Odpowiedzi:

108

Proste uwierzytelnianie podstawowe z waniliowym JavaScriptem (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

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

})

Uwaga: tego „oprogramowania pośredniego” można używać w dowolnym programie obsługi. Po prostu usuń next()i odwróć logikę. Zobacz poniższy przykład wyrażenia 1 lub historię edycji tej odpowiedzi.

Czemu?

  • req.headers.authorizationzawiera wartość „ Basic <base64 string>”, ale może być również pusty i nie chcemy, aby się nie powiódł, stąd dziwne połączenie|| ''
  • Węzeł nie wie atob()i btoa()dlategoBuffer

ES6 -> ES5

constjest po prostu var... tak jakby
(x, y) => {...}to po function(x, y) {...}
const [login, password] = ...split()prostu dwa varzadania w jednym

źródło inspiracji (korzysta z pakietów)


Powyższy przykład to super prosty przykład, który miał być bardzo krótki i szybko wdrażalny na serwerze placu zabaw. Ale jak wskazano w komentarzach, hasła mogą również zawierać znaki dwukropka :. Aby poprawnie wyodrębnić go z b64auth , możesz użyć tego.

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

Podstawowe uwierzytelnianie w jednym oświadczeniu

... z drugiej strony, jeśli używasz tylko jednego lub kilku loginów, jest to absolutne minimum, którego potrzebujesz: (nie musisz nawet analizować poświadczeń)

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: czy potrzebujesz mieć zarówno „bezpieczne”, jak i „publiczne” ścieżki? Rozważ użycie express.routerzamiast tego.

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
źródło
2
Najlepsze z partii ... :)
Anupam Basak
2
Nie używaj, .split(':')ponieważ będzie się dławić hasłami zawierającymi co najmniej jeden dwukropek. Takie hasła są ważne zgodnie z RFC 2617 .
Distortum
1
Możesz również użyć RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []dla części dwukropka.
adabru
3
Używanie !==do porównywania haseł naraża Cię na ataki czasowe. en.wikipedia.org/wiki/Timing_attack upewnij się, że używasz stałego porównania ciągów czasu.
hraban
1
Użyj Buffer.from() // for stringslub Buffer.alloc() // for numbersjako Buffer()przestarzałe ze względu na problemy z bezpieczeństwem.
Mr. Alien
71

TL; DR:

express.basicAuthzniknął
basic-auth-connectjest przestarzała
basic-authnie ma żadnej logiki
http-authjest przesadą
express-basic-authto, co chcesz

Więcej informacji:

Ponieważ używasz Express, możesz użyć express-basic-authoprogramowania pośredniego.

Zobacz dokumentację:

Przykład:

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
źródło
17
Zajęło mi trochę czasu, challenge: true
zanim wymyśliłem
1
@VitaliiZurian Dobra uwaga - dodałem to do odpowiedzi. Dzięki za wskazanie tego.
rsp
4
@rsp Czy wiesz, jak zastosować to tylko do określonych tras?
Jorge L Hernandez
Jeśli nie chcesz dodawać innych zależności, bardzo łatwo jest napisać podstawowe uwierzytelnianie ręcznie w jednej linii ...
Qwerty
jak wyglądałby adres URL klienta?
GGEv
57

Wiele oprogramowania pośredniego zostało wyciągniętych z rdzenia Express w wersji 4 i umieszczonych w oddzielnych modułach. Podstawowy moduł autoryzacji jest tutaj: https://github.com/expressjs/basic-auth-connect

Twój przykład musiałby po prostu zmienić się na ten:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
Brian Prodoehl
źródło
19
Ten moduł twierdzi, że jest przestarzały (choć sugerowana alternatywa wydaje się niezadowalająca)
Arnout Engelen
3
^^ absolutnie niezadowalające, jak w gęsto nieudokumentowanym. zerowy przykład użycia jako oprogramowanie pośredniczące, do którego prawdopodobnie jest dobre, ale wywołanie jest niedostępne. podany przez nich przykład jest świetny dla ogólności, ale nie dla informacji o użytkowaniu.
Wylie Kulik
Tak, ten jest przestarzały i chociaż zalecany ma mało dokumentów, kod jest bardzo prosty github.com/jshttp/basic-auth/blob/master/index.js
Loourr
1
Opisałem, jak korzystać z basic-authbiblioteki w tej odpowiedzi
Loourr
W jaki sposób istnieje cały moduł oparty na umieszczaniu hasła w postaci zwykłego tekstu w kodzie ? Przynajmniej zaciemnienie tego przez porównanie w base64 wydaje się nieznacznie lepsze.
user1944491
33

Użyłem kodu oryginału, basicAuthaby znaleźć odpowiedź:

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
źródło
10
ten moduł jest uważany za przestarzały, zamiast tego użyj jshttp / basic-auth (ten sam interfejs API, więc odpowiedź nadal obowiązuje)
Michael
32

W express 4.0 zmieniłem uwierzytelnianie podstawowe z http-auth , kod to:

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
źródło
1
To jest dosłownie plug and play. Doskonale.
sidonaldson
20

Wydaje się, że jest do tego wiele modułów, niektóre są przestarzałe.

Ten wygląda na aktywny:
https://github.com/jshttp/basic-auth

Oto przykład użycia:

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

Upewnij się, że authoprogramowanie pośredniczące zostało umieszczone we właściwym miejscu, żadne oprogramowanie pośrednie nie zostanie wcześniej uwierzytelnione.

Michael
źródło
Właściwie to jestem zwolennikiem „basic-auth-connect”, nazwa jest zła, ale pod względem funkcjonalności jest lepsza niż „basic-auth”. Wszystko, co robi, to parsowanie nagłówka autoryzacji. Nadal musisz implementsam protokół (inaczej wysłać poprawny nagłówek)
FDIM
Idealny! Dziękuję Ci za to. To zadziałało i wszystko ładnie wyjaśniło.
Tania Rascia
Próbowałem tego, ale ciągle prosi mnie o zalogowanie się przez ciągłą pętlę.
jdog
6

Możemy zaimplementować podstawową autoryzację bez konieczności posiadania żadnego modułu

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

Źródło: - http://www.dotnetcurry.com/nodejs/1231/basic-authentication-using-nodejs

VIKAS KOHLI
źródło
1

Express usunął tę funkcję i teraz zaleca używanie biblioteki basic-auth .

Oto przykład użycia:

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)

Aby wysłać żądanie do tej trasy, musisz dołączyć nagłówek autoryzacji sformatowany dla podstawowej autoryzacji .

Wysyłając najpierw żądanie curl, musisz zastosować kodowanie base64name:pass lub w tym przypadku, aladdin:opensesamektóre jest równeYWxhZGRpbjpvcGVuc2VzYW1l

Twoja prośba o zawinięcie będzie wyglądać następująco:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
Loourr
źródło
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
źródło
Mam nadzieję, że to rozwiąże problem, ale proszę dodać wyjaśnienie swojego kodu, aby użytkownik uzyskał doskonałe zrozumienie, czego naprawdę chce.
Jaimil Patel