Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Basic HTTP authentication with Node and Express 4

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();
    }
});
like image 646
Dov Avatar asked Oct 09 '22 02:10

Dov


People also ask

Does express support HTTP basic authentication?

To add basic auth capabilities to an Express app, we can use the express-basic-auth package. With it, we can check for a given username and password in the URL in our protected routes.

How do I give a basic auth in node JS?

Explanation: The first middleware is used for checking the authentication of the client when the server start and the client enter the localhost address. Initially req. headers. authorization is undefined and next() callback function return 401 status code unauthorized access to the browser.


1 Answers

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
like image 172
Qwerty Avatar answered Oct 11 '22 14:10

Qwerty