lionc / express-basic-auth Goto Github PK
View Code? Open in Web Editor NEWPlug & play basic auth middleware for express
Plug & play basic auth middleware for express
Is there a way to modify the response (headers particularly) beyond just setting a custom body?
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?
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.
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.
Current behavior supports:
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!'
});
}
}));
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
I have a couple public routes where I don't want there to be any baisc auth requirements.
Does this package offer a way to do that?
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 };
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?
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});\""
Is it posible to whitelist some endpoints of my express api to bypass the authentication/authorization using the module?
Can express-basic-auth be used with jwt? How would that work?
Thanks
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.
Lines 137 to 140 in dd17b4d
Lines 66 to 75 in dd17b4d
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 aWWW-Authenticate
header field (Section 11.6.1) containing at least one challenge applicable to the target resource.
I suggest changing this line
Line 30 in dd17b4d
to
const challenge = !!(options.challenge ?? true);
, and accordingly the documentation as well.
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 },
})
);
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.
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?
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.
Line 45 in 738ef5b
if(isAsync)
return authorizer(authentication.name, authentication.pass, authorizerCallback,req)
else if(!authorizer(authentication.name, authentication.pass))
return unauthorized()
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: -
userInputLength
intead of secretLength
when allocating secretBuffer
?&
instead of the logical &&
?Thanks
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.
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',
},
}));
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?
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.
I want to implement a feature where a user needs to enter their credentials again if they refresh the page or close the browser tab after logging in.
`
const basicAuth = require('express-basic-auth');
app.use(
/${routesPrefix}/v1/readme
,
basicAuth({
authorizer: myAuthorizer,
challenge: true,
}),
express.static(path.join(__dirname, '/readme')),
);
function myAuthorizer(username, password) {
try {
const userMatches = basicAuth.safeCompare(username, process.env.BASIC_AUTH_USER);
const passwordMatches = basicAuth.safeCompare(password, process.env.BASIC_AUTH_PASSWORD);
return userMatches && passwordMatches;
} catch (error) {
logger.error('myAuthorizer', error);
}
}
`
Expected Behavior:
After successfully logging in using express-basic-auth, if the user refreshes the page or closes the browser tab, they should be prompted to log in again.
Current Behavior:
The authentication works, but refreshing the page or closing the tab does not prompt the user to log in again.
Steps to Reproduce:
Log in using valid credentials.
Refresh the page or close the browser tab.
Notice that the page does not prompt for login credentials again.
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?
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();
...
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
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?
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;
}
};
I'm getting the error app.use() requires middleware functions
.
{
"express": "4.14.0",
"express-basic-auth": "1.0.1"
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.