Giter VIP home page Giter VIP logo

express-basic-auth's People

Contributors

lionc avatar toverux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

express-basic-auth's Issues

@types/express should be moved under devDependencies

Hi,
I think @types/express should be moved under devDependencies in package.json, because it is not necessary to run the module in production, rather it s needed only during development.
Due to this issue, some @types/... modules remain installed even after npm prune --production (which is supposed to remove the devDependencies), which is not so nice.
What do you think?

Require hashes instead of plain text passwords

Hi,
thanks for the library. I am implementing a simple auth mechanism but was wondering if there is any easy way to have bcrypt hashes in the code instead of the plain text passwords. Unfortunately, there is no built-in support like below.

basicAuth({
  useBcrypt: true,
  users: ALLOWED_USERS,
})

I ended with this implementation. It's not hard to do but I can imagine some developers starting with the programming may not be able to do that in a reasonable time or are not interested to do it in the first place because providing plain-text passwords in the code is so easy :)

import * as bcrypt from 'bcrypt';

basicAuth({
  authorizeAsync: true,
  authorizer: async (username, password, authorize) => {
    const passwordHash = ALLOWED_USERS[username];
    const passwordMatches = await bcrypt.compare(password, passwordHash);

    return authorize(null, passwordMatches);
  },
})

I like how you basically teach people about timing attacks but I think it should be noted also that storing plain text passwords is not a good idea. So what I would like to propose is to implement hashed based passwords by default to teach people about this best practice. Something like below. What do you think?

basicAuth({
  users: {user: '$2b$13$AL6K99UVLEjngKPgKST39O13E4CyjnaRX..qM/ij7F3IyAbL8LGri'},
})

I prepared a simple npm script to generate the password with the hash. You could create similar one to provide CLI for users to generate their hashes.

"password": "node -e \"const bcrypt = require('bcrypt'); const password = Array(25).fill('+-_!?,.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz').map((x) => x[Math.floor(Math.random() * x.length)]).join(''); const hash = bcrypt.hashSync(password, 13); console.log({password, hash});\""

Challenge: true

I cannot get my custom authorizer to work without setting challenge to true. When challenge is not true, all requests fail with a 401.

It appears when basic-auth fails, the code goes to the unauthorized method without looking for a custom authorizer.

Typescript example fails

The provided typescript usage produces a typescript error:

import * as basicAuth from 'express-basic-auth'
app.use(basicAuth(options), (req: basicAuth.IBasicAuthedRequest, res, next) => {
    res.end(`Welcome ${req.auth.user} (your password is ${req.auth.password})`)
    next()
})

Produces this error:

This expression is not callable.
Type 'typeof expressBasicAuth' has no call signatures.ts(2349)
service.ts(6, 1): Type originates at this import. A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Consider using a default import or import require here instead.

Allow previos authentication middlewares.

I think a good approach would be to check if req.auth exists already. If yes, a previous method already authenticated.

return function authMiddleware(req, res, next) {
		if (req.auth) return next();
...

Modify the response

Is there a way to modify the response (headers particularly) beyond just setting a custom body?

Add login request on authorizer

I need to setup a login request with axios as authentication method inside the express-basic-auth, to access my swagger. Is it possible? How can I do it? I tried with authorizer, but failed.

example:

app.use(['/${SWAGGER_API_PATH}'], basicAuth({ authorizer: myAuthorizer }));

function myAuthorizer(username, password) {
const response = axios.post('http://localhost:3000/api/auth', {username, password});

if(response.status === 200){
return true;
};
else{
return false;
}
};

Using express-basic-auth with router ?

I can't figure out how to use express-basic-auth correctly. If I make a POST-request to "/abort" with correct authorization everything seams to work correctly. But if I enter the wrong credentials in the header I get the correct "Credentials rejected" message.
But it still triggers the "/abort" endpoint and also gives med console.log outputs and 200 message:sucess

What am I missing ?
App.js

const getUnauthorizedResponse = (req) => {
  return req.auth
    ? `Credentials ${req.auth.user} : ${req.auth.password} rejected`
    : "No credentials provided";
};

app.use(
  basicAuth({
    users:  {"user":"password"} ,
    unauthorizedResponse: getUnauthorizedResponse,
  })
);

app.get("/", (req, res) => res.send("API Running"));

app.use("/api", require("./routes/api/abort").router);

abort.js

const express = require("express");
const router = express.Router();

router.post('/abort', async (req, res) => {
    try {
        const body = await req.body
        const hostname = body.hostname
        console.log(`Abort datacollection endpoint.\nHostname is ${hostname}`)
        return res.status(200).send({message:"success"})
    } catch (error) {
        console.log("error!!!")
        return res.status(404).send({message:"fail"})
    }
})

module.exports = { router };

401 responses MUST have a `WWW-Authenticate` header.

### Challenge
Per default the middleware will not add a `WWW-Authenticate` challenge header to
responses of unauthorized requests. You can enable that by adding `challenge: true`

function unauthorized() {
if(challenge) {
var challengeString = 'Basic'
var realmName = realm(req)
if(realmName)
challengeString += ' realm="' + realmName + '"'
res.set('WWW-Authenticate', challengeString)
}

The current default behavior, responding with the status code 401 without the WWW-Authenticate header field, violates RFC 9110. Do you have any particular reasons for the decision on the default behavior that is not RFC-compliant?

RFC 9110 โ€” HTTP semantics

15.5.2. 401 Unauthorized

The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource. The server generating a 401 response MUST send a WWW-Authenticate header field (Section 11.6.1) containing at least one challenge applicable to the target resource.


Suggestion

I suggest changing this line

var challenge = options.challenge != undefined ? !!options.challenge : false

to

const challenge = !!(options.challenge ?? true);

, and accordingly the documentation as well.

[Question] Blank/missing credentials

I am using a custom authorizer function.

What do you guys do when the username / password is missing or whitespace? Does the library handle that case and return 401, or must I do so manually?

[Question] safeCompare function

Hi,

I use this library to secure my NestJS Swagger endpoints, and it works great!
I wanted to see how it works by digging around in the source code, and I found something that seemed odd to me.

function safeCompare(userInput, secret) {
    const userInputLength = Buffer.byteLength(userInput)
    const secretLength = Buffer.byteLength(secret)

    const userInputBuffer = Buffer.alloc(userInputLength, 0, 'utf8')
    userInputBuffer.write(userInput)
    const secretBuffer = Buffer.alloc(userInputLength, 0, 'utf8') // Question 1
    secretBuffer.write(secret)

    return !!(timingSafeEqual(userInputBuffer, secretBuffer) & userInputLength === secretLength) // Question 2
}

Here're my questions: -

  1. Why does it use userInputLength intead of secretLength when allocating secretBuffer?
  2. Why does it use bitwise & instead of the logical &&?

Thanks

[BUG] error TS2688: Cannot find type definition file for 'express'.

Hi, I added this to my project and it gives me an error whilst building my TS file.

$ npm run build
> [email protected] build
> tsc

node_modules/express-basic-auth/express-basic-auth.d.ts:1:23 - error TS2688: Cannot find type definition file for 'express'.

1 /// <reference types="express" />
                        ~~~~~~~


Found 1 error in node_modules/express-basic-auth/express-basic-auth.d.ts:1

Can someone maybe help to come up with a solution or patch?

  • Miniontoby

Add the UTF-8 `charset` `auth-param`.

function unauthorized() {
if(challenge) {
var challengeString = 'Basic'
var realmName = realm(req)
if(realmName)
challengeString += ' realm="' + realmName + '"'
res.set('WWW-Authenticate', challengeString)
}

Now that UTF-8 has become the de facto universal standard, I suggest adding the UTF-8 charset auth-param defined by RFC 7617, to the WWW-Authenticate header field value express-basic-auth produces.

WWW-Authenticate: Basic realm="real", charset="UTF-8"

Enable custom unauthorized handling via middleware (req, res, next)

Background

Current behavior supports:

  • string (rendered as text in response)
  • object (rendered as JSON object in response)

Enhancement

Enable passing a normal middleware. This allows full customization of the response handling in the case of unauthorized:

  app.use(basicAuth({
    unauthorizedResponse: function(req, res, next) {
      // use an application's existing error handling stack
      if (req.auth) {
        next(new UnauthorizedError('Login required'));
        return;
      }
      // or send an html page
      if (req.accepts('html') {
        res.render('unauthorized');
        return;
      }
      // or whatever you like!
      res.status(403)
        send({
          message: 'That user will NEVER have access. Ever!'
        });
    }
  }));

TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received undefined

Migrating an application from Node 16 to Node 18, running the app tests - they fail with this:

TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received undefined at new NodeError (node:internal/errors:393:5) at Function.byteLength (node:buffer:740:11) at safeCompare (/Users/username/IdeaProjects/project/node_modules/express-basic-auth/index.js:9:33) at staticUsersAuthorizer (/Users/username/IdeaProjects/project/node_modules/express-basic-auth/index.js:42:43) at authMiddleware (/Users/username/IdeaProjects/project/node_modules/express-basic-auth/index.js:61:18) at Layer.handle [as handle_request] (/Users/username/IdeaProjects/project/node_modules/express/lib/router/layer.js:95:5) at next (/Users/username/IdeaProjects/project/node_modules/express/lib/router/route.js:144:13) at Route.dispatch (/Users/username/IdeaProjects/project/node_modules/express/lib/router/route.js:114:3) at Layer.handle [as handle_request] (/Users/username/IdeaProjects/project/node_modules/express/lib/router/layer.js:95:5) at /Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:284:15 at Function.process_params (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:346:12) at next (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:280:10) at expressInit (/Users/username/IdeaProjects/project/node_modules/express/lib/middleware/init.js:40:5) at Layer.handle [as handle_request] (/Users/username/IdeaProjects/project/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:328:13) at /Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:286:9 at Function.process_params (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:346:12) at next (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:280:10) at query (/Users/username/IdeaProjects/project/node_modules/express/lib/middleware/query.js:45:5) at Layer.handle [as handle_request] (/Users/username/IdeaProjects/project/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:328:13) at /Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:286:9 at Function.process_params (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:346:12) at next (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:280:10) at Function.handle (/Users/username/IdeaProjects/project/node_modules/express/lib/router/index.js:175:3) at Function.handle (/Users/username/IdeaProjects/project/node_modules/express/lib/application.js:181:10) at Server.app (/Users/username/IdeaProjects/project/node_modules/express/lib/express.js:39:9) at Server.emit (node:events:513:28) at parserOnIncoming (node:_http_server:1068:12) at HTTPParser.parserOnHeadersComplete (node:_http_common:117:17)

Still looking to find the root cause.

Create open endpoints

Is it posible to whitelist some endpoints of my express api to bypass the authentication/authorization using the module?

Challenge doesn't show up on lambda / serverless API

I'm running an express API with an AWS Lambda proxy function using @vendia/serverless-express. Locally the route produces the challenge prompt, but when deployed I only get the 401 unauthorized and am never prompted to enter the login information.

Does something else need to be done with serverless APIs or something specifically with AWS / Lambda?

router.use(
    '/docs',
    basicAuth({
      users: { admin: 'supersecret' },
      challenge: true,
    }),
    swaggerUi.serve,
    swaggerUi.setup(spec, {
      swaggerOptions: { persistAuthorization: true },
    })
  );

How to pass additional data from myAsyncAuthorizer() to req.auth?

Hi,

Thanks for this module!

In myAsyncAuthorizer() we load some data about that user. We'd like to pass that data forward to be available in req.auth for further use in the authenticated route.

How to accomplish this, please? There's no mention in the README.

Noob question

To make this work, do I need to create a login page with form fields where I type the admin & password into it, then have those values added to the headers of an auth page request to get authorized?

I understand there's multiple ways to go about this, but I just don't understand how I'm supposed to supply a username and password to get authorized. I see that it parses the headers in order to verify, so I guess I need to write some code that will take my submitted values, and put them into headers for the request that gets authorized?

Just confused if I'm supposed to be using like a post request with the headers to my auth page, or if its as simple as putting URL parameters (example.com/auth?id=admin&password=secret123) and parsing them. I'm all kinds of confused.. I'm sorry

Backoff retry algorithm

To avoid bad actors abusing the auth and trying to brute-force their way in, a back-off algorithm based on IPs would be nice to have

TypeError: "string" must be a string, Buffer, or ArrayBuffer

Version: 1.2.0

In my project basic-auth works just fine, with a custom Authorizer for specific routes.

But when I'm using this code,

app.get('/api/auth', basicAuth({
  users: { 'username': 'password'},
  challenge: true,
  realm: 'domain',
}))

for a unique endpoint, I'll get the following error:

TypeError: "string" must be a string, Buffer, or ArrayBuffer
    at Function.byteLength (buffer.js:481:11)
    at safeCompare (/opt/project-name/node_modules/express-basic-auth/index.js:9:33)
    at staticUsersAuthorizer (/opt/project-name/node_modules/express-basic-auth/index.js:42:43)
    at authMiddleware (/opt/project-name/node_modules/express-basic-auth/index.js:61:18)
    at Layer.handle [as handle_request] (/opt/project-name/node_modules/express/lib/router/layer.js:95:5)
    at next (/opt/project-name/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/opt/project-name/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/opt/project-name/node_modules/express/lib/router/layer.js:95:5)
    at /opt/project-name/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/opt/project-name/node_modules/express/lib/router/index.js:335:12)

Basically, what I'm doing is: User can enter a specific endpoint, the browser native challenge input pops up, after entering the credentials he gets to pass...

But I keep getting this error.

[Question] Can I get request.user?

In passport and other similar libraries, once username/password are checked, and therefore the request is authorised, the user object is attached to the request - req.user can thus be used in the route handler without another database lookup.

Can I do the same here? The docs say that req.auth exposes username and password, but how do I attach the user object as well? I thought I could return it from the authentication function and it would be attached, but it doesn't seem to work that way.

Custom authorizer is not called without Authorization header

The custom authorizer is not called if my request has not Authorization header and the request is rejected automaticaly with "401 NO AUTHORIZED" message which is not what I was expected.

This forces me to have to use dummy auth data with the aim of invoked my custom authorized.

I would like the custom authorized is called without refusing the request even if the header is empty because that is precisely the work of the custom authorized, reject or allow each request, isn't it?

Why is the reason for this decision?

[Question] How does this differ from Passport?

I was planning to do basic auth using passport-http followed by token exchange using passport-jwt. But passport hasn't been updated in years and the documentation is a mess.

So I was looking for something else with good documentation, a large user base, typescript support and that is being maintained... and found this! :)

I'm curious how it differs from passport?

feature request - set auth username on request object.

It seems likely that most users of this module will want to know which user actually authenticated, which requires parsing the Authorization header, which this module is already doing.

I suggest setting req.authUser to the username part of the decoded header upon a successful authentication. Including it on failed auths seems like a security bug magnet, so I'd be inclined not to do that.

Blank page in safari when pw contains !

Funny Story ๐Ÿ™‚

app.use(basicAuth({
users: { 'admin': 'supersecret' }
}))

When the supersecret contains an exclamation mark Safari shows a blank page ๐Ÿ™‚ question marks for example work.
The issue does not appear on any android Version but on every ios Version ^^ maybe the internal base64 is messed up by special special characters?

can not use var as user credential

i can not use var as user credential

const username = process.env.APP_USERNAME;
const password = process.env.APP_PASSWORD;
app.use(basicAuth({
  users: { username: password },
  unauthorizedResponse: {
    message: 'Bad credentials',
  },
}));

because the username is considered as a property, instead of variable
maybe you can make it be like this:

const username = process.env.APP_USERNAME;
const password = process.env.APP_PASSWORD;
app.use(basicAuth({
  users: {
    username: username,
    password: password,
  },
  unauthorizedResponse: {
    message: 'Bad credentials',
  },
}));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.