164

It looks like implementing basic HTTP authentication with Express v3 was trivial:

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

Version 4 (I'm using 4.2) removed the basicAuth middleware, though, so I'm a little stuck. I have the following code, but it doesn't cause the browser to prompt the user for credentials, which is what I'd like (and what I imagine the old method did):

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
  • 15,530
  • 13
  • 76
  • 177
  • 3
    Shameless plug: I maintain a fairly popular module that makes that easy and has most standard features you would need: [express-basic-auth](https://www.npmjs.com/package/express-basic-auth) – LionC Feb 09 '18 at 16:45
  • I recently forked @LionC 's package because I had to adapt it (enabling context-aware authorizers) in a ultra-short span of time for a company project: https://www.npmjs.com/package/spresso-authy – castarco Jul 08 '19 at 07:46
  • 1
    @LionC it is unclear from docs of `express-basic-auth` how to apply it to just one route. – Oleg Abrazhaev Jul 14 '21 at 08:24

10 Answers10

185

Simple Basic Auth with vanilla JavaScript (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: This "middleware" can be used in any handler. Just remove next() and reverse the logic. See the 1-statement example below, or the edit history of this answer.

Why?

  • req.headers.authorization contains the value "Basic <base64 string>", but it can also be empty and we don't want it to fail, hence the weird combo of || ''
  • Node doesn't know atob() and btoa(), hence the Buffer

ES6 -> ES5

const is just var .. sort of
(x, y) => {...} is just function(x, y) {...}
const [login, password] = ...split() is just two var assignments in one

source of inspiration (uses packages)


The above is a super simple example that was intended to be super short and quickly deployable to your playground server. But as was pointed out in the comments, passwords can also contain colon characters :. To correctly extract it from the b64auth, you can use this.
  // 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(/(.*?):(.*)/) || []

Basic auth in one statement

...on the other hand, if you only ever use one or very few logins, this is the bare minimum you need: (you don't even need to parse the credentials at all)

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: do you need to have both "secure" and "public" paths? Consider using express.router instead.

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
  • 29,062
  • 22
  • 108
  • 136
  • 7
    Don't use `.split(':')` because it will choke on passwords containing at least one colon. Such passwords are valid according to [RFC 2617](http://www.ietf.org/rfc/rfc2617.txt). – Steve Feb 04 '18 at 11:33
  • 2
    You can also use RegExp `const [_, login, password] = strauth.match(/(.*?):(.*)/) || []` for the colon part. – adabru Aug 04 '18 at 09:44
  • 4
    Using `!==` to compare passwords leaves you vulnerable to timing attacks. https://en.wikipedia.org/wiki/Timing_attack make sure you use a constant time string compare. – hraban Mar 01 '19 at 09:56
  • 1
    Use `Buffer.from() // for strings` or `Buffer.alloc() // for numbers` as `Buffer()` is deprecated due to security issues. – Mr. Alien Feb 25 '20 at 06:56
  • 2
    Downvoted because of the timing attack vulnerability. This should use https://nodejs.org/api/crypto.html#crypto_crypto_timingsafeequal_a_b instead. See for example how express-basic-auth implemented this, https://github.com/LionC/express-basic-auth/blob/fa600c90707a69f1f3a0fa9f0295f412fc207e4e/index.js#L5-L17. – Thibaud Colas Apr 18 '20 at 21:12
  • 1
    @ThibaudColas Can you please link to an article for others to see, explaining why this is an issue on a remote http server, where among other aspects, for example, the response time introduces a non-deterministic time delay, which in my laic opinion obfuscates this vulnerability? – Qwerty Apr 20 '20 at 11:42
  • 2
    @Qwerty I don’t have a good single article on the topic (does Wikipedia help? see https://en.wikipedia.org/wiki/Timing_attack shared by @hraban). Your opinion is on point – network factors make timing attacks less practical. But relying on this would be a case of "security through obscurity" – it might help here and there, but it’s not as good of a protection as implementing constant-time comparison. See https://nvd.nist.gov/vuln/detail/CVE-2015-7576 for a similar vulnerability in Rails, and how its severity is rated. – Thibaud Colas Apr 20 '20 at 16:07
  • A _timing attack_ here means _figuring out the password by measuring how long the string comparison takes_. `"0123456789" === "??????????"` would fail slightly quicker than `"0123456789" === "012345678?"` because the numbers of characters that need to be compared to tell the equalities (1 and 10 respectively) are different. – Константин Ван Nov 27 '22 at 05:36
  • I learned a lot reading through this. – N Altun Jan 30 '23 at 07:54
129

TL;DR:

express.basicAuth is gone
basic-auth-connect is deprecated
basic-auth doesn't have any logic
http-auth is an overkill
express-basic-auth is what you want

More info:

Since you're using Express then you can use the express-basic-auth middleware.

See the docs:

Example:

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!
}));
Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
  • 22
    Took me a while to figure out about `challenge: true` option – Vitalii Zurian Mar 26 '18 at 21:03
  • 1
    @VitaliiZurian Good point - I added it to the answer. Thanks for pointing it out. – rsp Mar 27 '18 at 21:50
  • 10
    @rsp Do you know how to apply this only to specific routes? – Jorge L Hernandez May 12 '18 at 21:48
  • If you don't want to add other dependencies, it's very easy to write basic auth by hand on one line... – Qwerty Apr 24 '19 at 20:40
  • how would the client url look like ? – GGEv Nov 07 '19 at 16:31
  • This answer was the solution to the problem I was looking for, however it strangely changed the behavior of static files, which now have precedence over routes, I had to delete index.html for this to work for some reason. Node is weird! – Felipe Valdes Sep 14 '20 at 09:59
  • How safe would this be to protect my Heroku app that I have deployed for personal usage? I have in the past been a victim of having my AWS keys stolen and used so I am asking here as a precaution since my personal app contains sensitive info. – philosopher Nov 11 '20 at 07:03
  • Best answer imo. Fast and helpful – Igor Kurkov Feb 16 '21 at 17:03
  • Qwerty's comment and answer above are great as exercises. However, there are very few scenarios where rolling your own authentication strategy for a real, publicly available application is a good idea; there's too much room for error. The _vast majority_ of users are better off using a well documented, widely deployed solution like express-basic-auth. – pdoherty926 Sep 01 '21 at 14:30
  • Any idea of how to replace that dialog from the challenge for a proper login.html page ? – Pedro Bezanilla Jan 31 '23 at 22:17
59

A lot of the middleware was pulled out of the Express core in v4, and put into separate modules. The basic auth module is here: https://github.com/expressjs/basic-auth-connect

Your example would just need to change to this:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));
Brian Prodoehl
  • 615
  • 1
  • 4
  • 2
  • 21
    This module claims to be deprecated (though the alternative it suggests seems unsatisfying) – Arnout Engelen Jul 31 '14 at 20:54
  • 3
    ^^ absolutely unsatisfying as in densely undocumented. zero example of use as middleware, which it's probably good for, but the invocation is unavailable. the example they give is great for generality, but not for usage info. – Wylie Kulik Oct 05 '16 at 13:36
  • Yeah this one is deprecated, and while the one recommended is low on docs, the code is very simple https://github.com/jshttp/basic-auth/blob/master/index.js – Loourr Oct 27 '17 at 17:06
  • 1
    I've described how to use the `basic-auth` library in [this answer](https://stackoverflow.com/a/46981895/1470897) – Loourr Oct 27 '17 at 19:15
  • How does an entire module exist based around *putting the password in clear text in the code*?? At least obscuring it by comparing in base64 seems marginally better. – user1944491 Oct 02 '19 at 11:29
36

I used the code for the original basicAuth to find the answer:

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
  • 15,530
  • 13
  • 76
  • 177
  • 12
    this module is considered deprecated, use [jshttp/basic-auth](https://github.com/jshttp/basic-auth) instead (same api so answer still applies) – Michael Jan 01 '15 at 09:13
34

I changed in express 4.0 the basic authentication with http-auth, the code is:

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
  • 441
  • 4
  • 3
21

There seems to be multiple modules to do that, some are deprecated.

This one looks active:
https://github.com/jshttp/basic-auth

Here's a use example:

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

Make sure you put the auth middleware in the correct place, any middleware before that will not be authenticated.

Michael
  • 22,196
  • 33
  • 132
  • 187
  • I am actually in favor of 'basic-auth-connect', name is bad but functionality wise it it is better than 'basic-auth'. All the latter does, is parse authorization header. You still have to `implement` the protocol yourself (aka send correct header) – FDIM Apr 22 '15 at 07:24
  • Perfect! Thank you for this. This worked and explained everything nicely. – Tania Rascia Nov 13 '17 at 15:48
  • I tried this, but it just keeps asking me to login via continous loop. – jdog Feb 13 '18 at 16:05
7
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);
SB ADAMS
  • 71
  • 1
  • 3
  • 1
    Hope It will solve issue but please add explanation of your code with it so user will get perfect understanding which he/she really wants. – Jaimil Patel Jun 01 '20 at 04:59
7

install the express-basic-auth dependency:

 npm i express-basic-auth

Require the auth package where you create your app

const app = require('express')();
const basicAuth = require('express-basic-auth');

and setup the middleware like this:

app.use(basicAuth({
    users: { 'my-username': 'my-password' },
    challenge: true,
}));
De Bonheur
  • 742
  • 10
  • 13
6

We can implement the basic authorization without needing any 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
  • 8,164
  • 4
  • 50
  • 61
1

Express has removed this functionality and now recommends you use the basic-auth library.

Here's an example of how to use:

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)

To send a request to this route you need to include an Authorization header formatted for basic auth.

Sending a curl request first you must take the base64 encoding of name:pass or in this case aladdin:opensesame which is equal to YWxhZGRpbjpvcGVuc2VzYW1l

Your curl request will then look like:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/
Loourr
  • 4,995
  • 10
  • 44
  • 68