Autenticación HTTP básica con Node y Express 4

107

Parece que implementar la autenticación HTTP básica con Express v3 fue trivial:

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

Sin basicAuthembargo, la versión 4 (estoy usando 4.2) eliminó el middleware, así que estoy un poco atascado. Tengo el siguiente código, pero no hace que el navegador solicite al usuario las credenciales, que es lo que me gustaría (y lo que imagino que hizo el método anterior):

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
fuente
2
Enchufe desvergonzado: mantengo un módulo bastante popular que lo hace fácil y tiene la mayoría de las funciones estándar que necesitaría: express-basic-auth
LionC
Recientemente bifurqué el paquete de @LionC porque tuve que adaptarlo (habilitando autorizadores sensibles al contexto) en un lapso de tiempo ultra corto para un proyecto de empresa: npmjs.com/package/spresso-authy
castarco

Respuestas:

108

Autenticación básica simple con JavaScript vainilla (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: este "middleware" se puede utilizar en cualquier controlador. Simplemente elimine next()e invierta la lógica. Vea el ejemplo de 1 declaración a continuación o el historial de edición de esta respuesta.

¿Por qué?

  • req.headers.authorizationcontiene el valor " Basic <base64 string>", pero también puede estar vacío y no queremos que falle, de ahí la extraña combinación de|| ''
  • El nodo no lo sabe atob()y btoa(), por lo tanto, elBuffer

ES6 -> ES5

constes var... más
(x, y) => {...}o menos function(x, y) {...}
const [login, password] = ...split()es solo dos vartareas en una

fuente de inspiración (usa paquetes)


El anterior es un ejemplo súper simple que estaba destinado a ser súper corto y rápidamente implementable en su servidor de juegos. Pero como se señaló en los comentarios, las contraseñas también pueden contener dos puntos :. Para extraerlo correctamente de b64auth , puede usar esto.

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

Autenticación básica en una declaración

... por otro lado, si solo usa uno o muy pocos inicios de sesión, este es el mínimo que necesita: (ni siquiera necesita analizar las credenciales)

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

PD: ¿necesita tener rutas "seguras" y "públicas"? Considere usar en su express.routerlugar.

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
fuente
2
Lo mejor del lote ... :)
Anupam Basak
2
No lo use .split(':')porque se ahogará con contraseñas que contengan al menos dos puntos. Dichas contraseñas son válidas de acuerdo con RFC 2617 .
Distortum
1
También puede utilizar RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []para la parte de los dos puntos.
adabru
3
El uso !==para comparar contraseñas lo deja vulnerable a los ataques de tiempo. en.wikipedia.org/wiki/Timing_attack asegúrese de utilizar una comparación de cadenas de tiempo constante.
hraban
1
Use Buffer.from() // for stringso Buffer.alloc() // for numberscomo Buffer()está en desuso debido a problemas de seguridad.
Mr. Alien
71

TL; DR:

express.basicAuthse ha ido
basic-auth-connectestá en desuso
basic-authno tiene ninguna lógica
http-authes una exageración
express-basic-authes lo que quieres

Más información:

Como está utilizando Express, puede utilizar el express-basic-authmiddleware.

Ver los documentos:

Ejemplo:

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
fuente
17
Me tomó un tiempo descubrir la challenge: trueopción
Vitalii Zurian
1
@VitaliiZurian Buen punto: lo agregué a la respuesta. Gracias por mencionarlo.
rsp
4
@rsp ¿Sabes cómo aplicar esto solo a rutas específicas?
Jorge L Hernandez
Si no desea agregar otras dependencias, es muy fácil escribir la autenticación básica a mano en una línea ...
Qwerty
¿Cómo sería la URL del cliente?
GGEv
57

Gran parte del middleware se extrajo del núcleo Express en v4 y se colocó en módulos separados. El módulo de autenticación básico está aquí: https://github.com/expressjs/basic-auth-connect

Su ejemplo solo necesitaría cambiar a esto:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
Brian Prodoehl
fuente
19
Este módulo afirma estar obsoleto (aunque la alternativa que sugiere parece insatisfactoria)
Arnout Engelen
3
^^ absolutamente insatisfactorio como en densamente indocumentado. cero ejemplo de uso como middleware, para lo que probablemente sea bueno, pero la invocación no está disponible. el ejemplo que dan es excelente para la generalidad, pero no para la información de uso.
Wylie Kulik
Sí, este está en desuso, y aunque el recomendado es bajo en documentos, el código es muy simple github.com/jshttp/basic-auth/blob/master/index.js
Loourr
1
Describí cómo usar la basic-authbiblioteca en esta respuesta
Loourr
¿Cómo existe un módulo completo basado en poner la contraseña en texto sin cifrar en el código ? Al menos oscurecerlo comparando en base64 parece un poco mejor.
user1944491
33

Usé el código del original basicAuthpara encontrar la respuesta:

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
fuente
10
este módulo se considera obsoleto, use jshttp / basic-auth en su lugar (la misma api, por lo que la respuesta aún se aplica)
Michael
32

Cambié en express 4.0 la autenticación básica con http-auth , el código es:

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
fuente
1
Esto es literalmente plug and play. Excelente.
sidonaldson
20

Parece que hay varios módulos para hacer eso, algunos están en desuso.

Este parece activo:
https://github.com/jshttp/basic-auth

Aquí hay un ejemplo de uso:

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

Asegúrese de colocar el authmiddleware en el lugar correcto, cualquier middleware anterior no será autenticado.

Miguel
fuente
De hecho, estoy a favor de 'basic-auth-connect', el nombre es malo pero en cuanto a funcionalidad es mejor que 'basic-auth'. Todo lo que hace este último es analizar el encabezado de autorización. Aún debe realizar implementel protocolo usted mismo (también conocido como enviar el encabezado correcto)
FDIM
¡Perfecto! Gracias por esto. Esto funcionó y explicó todo muy bien.
Tania Rascia
Intenté esto, pero sigue pidiéndome que inicie sesión a través de un bucle continuo.
jdog
6

Podemos implementar la autorización básica sin necesidad de ningún módulo

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

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

VIKAS KOHLI
fuente
1

Express ha eliminado esta funcionalidad y ahora le recomienda utilizar la biblioteca de autenticación básica .

Aquí hay un ejemplo de cómo usarlo:

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)

Para enviar una solicitud a esta ruta, debe incluir un encabezado de autorización con el formato de autenticación básica.

Al enviar una solicitud curl primero, debe tomar la codificación base64 de name:passo, en este caso, aladdin:opensesameque es igual aYWxhZGRpbjpvcGVuc2VzYW1l

Su solicitud de curl se verá así:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
Loourr
fuente
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
fuente
Espero que resuelva el problema, pero agregue una explicación de su código para que el usuario comprenda perfectamente lo que realmente quiere.
Jaimil Patel