Authentification HTTP de base avec Node et Express 4

107

Il semble que l'implémentation de l'authentification HTTP de base avec Express v3 était triviale:

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

La version 4 (j'utilise 4.2) a supprimé le basicAuthmiddleware, donc je suis un peu coincé. J'ai le code suivant, mais cela n'oblige pas le navigateur à demander à l'utilisateur ses informations d'identification, ce que j'aimerais (et ce que j'imagine que l'ancienne méthode faisait):

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
la source
2
Prise sans vergogne: je maintiens un module assez populaire qui facilite les
choses
J'ai récemment forké le package de @LionC parce que je devais l'adapter (activer les autorisateurs contextuels) dans un laps de temps ultra-court pour un projet d'entreprise: npmjs.com/package/spresso-authy
castarco

Réponses:

108

Authentification simple de base avec JavaScript vanille (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

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

})

note: ce "middleware" peut être utilisé dans n'importe quel gestionnaire. Retirez simplement next()et inversez la logique. Voir l' exemple à 1 instruction ci-dessous ou l' historique des modifications de cette réponse.

Pourquoi?

  • req.headers.authorizationcontient la valeur " Basic <base64 string>", mais elle peut aussi être vide et nous ne voulons pas qu'elle échoue, d'où l'étrange combo de|| ''
  • Node ne sait pas atob()et btoa(), par conséquentBuffer

ES6 -> ES5

constest juste var.. sorte
(x, y) => {...}est juste function(x, y) {...}
const [login, password] = ...split()est juste deux varmissions dans un

source d'inspiration (utilise des packages)


Ce qui précède est un exemple super simple qui devait être super court et rapidement déployable sur votre serveur de terrain de jeu. Mais comme cela a été souligné dans les commentaires, les mots de passe peuvent également contenir des caractères deux-points :. Pour l'extraire correctement du b64auth , vous pouvez l'utiliser.

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

Authentification de base en une seule instruction

... d'un autre côté, si vous n'utilisez qu'une ou très peu de connexions, c'est le strict minimum dont vous avez besoin: (vous n'avez même pas besoin d'analyser les informations d'identification du tout)

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: avez-vous besoin d'avoir à la fois des chemins "sécurisés" et "publics"? Pensez à utiliser à la express.routerplace.

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
la source
2
Best of the lot ... :)
Anupam Basak
2
Ne pas utiliser .split(':')car il s'étouffera avec les mots de passe contenant au moins un deux-points. Ces mots de passe sont valides selon la RFC 2617 .
Distortum
1
Vous pouvez également utiliser RegExp const [_, login, password] = strauth.match(/(.*?):(.*)/) || []pour la partie deux-points.
adabru
3
Utiliser !==pour comparer les mots de passe vous rend vulnérable aux attaques de synchronisation. fr.wikipedia.org/wiki/Timing_attack assurez-vous d'utiliser une comparaison de chaîne à temps constant.
hraban
1
Utilisez Buffer.from() // for stringsou Buffer.alloc() // for numberstel Buffer()quel en raison de problèmes de sécurité.
M. Alien le
71

TL; DR:

express.basicAuthest parti
basic-auth-connectest obsolète
basic-authn'a aucune logique
http-authest exagéré
express-basic-authest ce que vous voulez

Plus d'informations:

Puisque vous utilisez Express, vous pouvez utiliser le express-basic-authmiddleware.

Voir la documentation:

Exemple:

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
la source
17
Il m'a challenge: true
fallu
1
@VitaliiZurian Bon point - je l'ai ajouté à la réponse. Merci de l'avoir signalé.
rsp
4
@rsp Savez-vous comment appliquer cela uniquement à des itinéraires spécifiques?
Jorge L Hernandez
Si vous ne voulez pas ajouter d'autres dépendances, il est très facile d'écrire l'authentification de base à la main sur une seule ligne ...
Qwerty
à quoi ressemblerait l'URL du client?
GGEv
57

Une grande partie du middleware a été retirée du cœur d'Express dans la v4 et placée dans des modules séparés. Le module d'authentification de base est ici: https://github.com/expressjs/basic-auth-connect

Votre exemple devrait simplement changer pour ceci:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
Brian Prodoehl
la source
19
Ce module prétend être obsolète (bien que l'alternative qu'il suggère semble insatisfaisante)
Arnout Engelen
3
^^ absolument insatisfaisant comme dans densément non documenté. zéro exemple d'utilisation en tant que middleware, pour lequel il est probablement bon, mais l'invocation n'est pas disponible. l'exemple qu'ils donnent est excellent pour la généralité, mais pas pour les informations d'utilisation.
Wylie Kulik
Oui, celui-ci est obsolète, et bien que celui recommandé soit faible en documents, le code est très simple github.com/jshttp/basic-auth/blob/master/index.js
Loourr
1
J'ai décrit comment utiliser la basic-authbibliothèque dans cette réponse
Loourr
Comment existe-t-il un module entier basé sur la mise en texte clair du mot de passe dans le code ? Au moins l'obscurcir en comparant en base64 semble légèrement meilleur.
user1944491
33

J'ai utilisé le code de l'original basicAuthpour trouver la réponse:

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
la source
10
ce module est considéré comme obsolète, utilisez plutôt jshttp / basic-auth (la même api donc la réponse s'applique toujours)
Michael
32

J'ai changé dans express 4.0 l'authentification de base avec http-auth , le code est:

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
la source
1
C'est littéralement plug and play. Excellent.
sidonaldson
20

Il semble y avoir plusieurs modules pour faire cela, certains sont obsolètes.

Celui-ci a l'air actif:
https://github.com/jshttp/basic-auth

Voici un exemple d'utilisation:

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

Assurez-vous de placer le authmiddleware au bon endroit, tout middleware antérieur ne sera pas authentifié.

Michael
la source
Je suis en fait en faveur de 'basic-auth-connect', le nom est mauvais mais en termes de fonctionnalités, il est meilleur que 'basic-auth'. Tout ce que fait ce dernier, c'est analyser l'en-tête d'autorisation. Vous devez toujours implementle protocole vous-même (alias envoyer un en-tête correct)
FDIM
Parfait! Merci pour ça. Cela a fonctionné et expliqué tout bien.
Tania Rascia
J'ai essayé cela, mais cela ne cesse de me demander de me connecter via une boucle continue.
jdog
6

Nous pouvons implémenter l'autorisation de base sans avoir besoin d'aucun module

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

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

VIKAS KOHLI
la source
1

Express a supprimé cette fonctionnalité et vous recommande maintenant d'utiliser la bibliothèque de base-auth .

Voici un exemple d'utilisation:

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)

Pour envoyer une demande à cette route, vous devez inclure un en- tête d'autorisation formaté pour l'authentification de base.

En envoyant d'abord une requête curl, vous devez prendre le codage base64 de name:passou dans ce cas aladdin:opensesamequi est égal àYWxhZGRpbjpvcGVuc2VzYW1l

Votre demande de curl ressemblera alors à:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
Loourr
la source
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
la source
J'espère que cela résoudra le problème, mais veuillez ajouter une explication de votre code afin que l'utilisateur comprenne parfaitement ce qu'il veut vraiment.
Jaimil Patel le