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

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

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

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

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

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

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 = {
  '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

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


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


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/

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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.