
Research
Malicious fezbox npm Package Steals Browser Passwords from Cookies via Innovative QR Code Steganographic Technique
A malicious package uses a QR code as steganography in an innovative technique.
Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.
Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers. ESM and browser friendly.
npm i auth-vir
Hash a user created password:
import {hashPassword} from 'auth-vir';
/** When a user creates or resets their password, hash it before storing it in your database. */
const hashedPassword = await hashPassword('user input password');
/** Store `hashedPassword` in your database. */
Compare a stored password hash for login checking:
import {doesPasswordMatchHash} from 'auth-vir';
if (
!(await doesPasswordMatchHash({
hash: 'hash from database',
password: 'user input password for login',
}))
) {
throw new Error('Login failure.');
}
Use this on your host / server / backend to authenticate client / frontend requests.
csrfTokenHeaderName
(or just 'csrf-token'
) header via CORS headers with either of the following options:
customHeaders: [csrfTokenHeaderName]
in implementService
from @rest-vir/implement-service
.Access-Control-Allow-Headers
to (at least) csrfTokenHeaderName
.Access-Control-Allow-Origin
header (it cannot be *
) and properly implement CORS headers and responses.npx auth-vir
: the generated keys will be printed to your console.await generateNewJwtKeys()
(imported from this package) in your code.parseJwtKeys
: await parseJwtKeys(stringKeys)
.parseJwtKeys
in all auth functionality:
generateSuccessfulLoginHeaders
: after a user successfully logs in, run this function and attach the output headers to the Response
object.extractUserIdFromRequestHeaders
: to verify an authenticated user Request
object (make sure to properly attach all auth in the client by following the below Client / frontend side guide).Here's a full example of how to use all host / server / backend side auth functionality:
import {type ClientRequest, type ServerResponse} from 'node:http';
import {
doesPasswordMatchHash,
extractUserIdFromRequestHeaders,
generateNewJwtKeys,
generateSuccessfulLoginHeaders,
hashPassword,
parseJwtKeys,
type CookieParams,
type CreateJwtParams,
} from 'auth-vir';
/**
* Use this for a /login endpoint.
*
* This verifies a user's login credentials and generate the auth cookie and CSRF token.
*/
export async function handleLogin(
userRequestData: Readonly<{username: string; password: string}>,
response: ServerResponse,
) {
const user = findUserInDatabaseByUsername(userRequestData.username);
if (
!(await doesPasswordMatchHash({
hash: user.hashedPassword,
password: userRequestData.password,
}))
) {
throw new Error('Credentials mismatch.');
}
const authHeaders = await generateSuccessfulLoginHeaders(user.id, cookieParams);
response.setHeaders(new Headers(authHeaders));
}
/**
* Use this for a /sign-up endpoint.
*
* This creates a new user, stores their securely hashed password in the database, and generates the
* auth cookie and CSRF token.
*/
export async function createUser(
userRequestData: Readonly<{username: string; password: string}>,
response: ServerResponse,
) {
const newUser = await createUserInDatabase(userRequestData);
const authHeaders = await generateSuccessfulLoginHeaders(newUser.id, cookieParams);
response.setHeaders(new Headers(authHeaders));
}
/**
* Use this all endpoints that require an authenticated user.
*
* This loads the current user from their auth cookie and CSRF token.
*/
export async function getAuthenticatedUser(request: ClientRequest) {
const userId = await extractUserIdFromRequestHeaders(request.getHeaders(), jwtParams);
const user = userId ? findUserInDatabaseById(userId) : undefined;
if (!userId || !user) {
throw new Error('Unauthorized.');
}
return user;
}
/**
* # ===========
*
* Helpers
*
* # ===========
*/
async function loadSecretJwtKeys() {
/**
* This should load your saved JWT keys from a non-committed config file or a secrets manager
* (like AWS Secrets Manager).
*/
return await generateNewJwtKeys();
}
const jwtParams: Readonly<CreateJwtParams> = {
audience: 'server context',
jwtDuration: {
hours: 2,
},
issuer: 'server login',
jwtKeys: await parseJwtKeys(await loadSecretJwtKeys()),
};
const cookieParams: CookieParams = {
cookieDuration: {
hours: 2,
},
hostOrigin: 'https://your-backend-origin.example.com',
jwtParams,
};
function findUserInDatabaseByUsername(username: string) {
/** This should connect to your database and find a user matching the given username. */
return {
/** This should be retrieved from your database. */
id: 'some id',
username,
/** This should be retrieved from your database. */
hashedPassword: 'hash retrieved from database',
};
}
function findUserInDatabaseById(userId: string): undefined | {id: string; username: string} {
/** This should connect to your database and find a user matching the given user id. */
return {
id: userId,
/** This should be retrieved from your database. */
username: 'some username',
};
}
async function createUserInDatabase(
userRequestData: Readonly<{username: string; password: string}>,
) {
const hashedPassword = await hashPassword(userRequestData.password);
if (!hashedPassword) {
throw new Error('Password too long.');
}
/**
* Store the new username and hashedPassword in your database and return the new user id.
*
* @example
*
* // using the Prisma ORM:
* return (
* await prismaClient.user.create({
* data: {
* username: userRequestData.username,
* hashedPassword,
* },
* select: {
* id: true,
* },
* })
* ).id;
*/
return {
id: 'some new id',
};
}
Use this on your client / frontend for storing and sending session authorization.
{credentials: 'include'}
set on the request.Response
from step 1 into handleAuthResponse
.{credentials: 'include'}
and include {headers: {[csrfTokenHeaderName]: getCurrentCsrfToken()}}
.wipeCurrentCsrfToken()
Here's a full example of how to use all the client / frontend side auth functionality:
import {HttpStatus} from '@augment-vir/common';
import {
csrfTokenHeaderName,
getCurrentCsrfToken,
handleAuthResponse,
wipeCurrentCsrfToken,
} from 'auth-vir';
/** Call this when the user logs in for the first time this session. */
export async function sendLoginRequest(
userLoginData: {username: string; password: string},
loginUrl: string,
) {
if (getCurrentCsrfToken()) {
throw new Error('Already logged in.');
}
const response = await fetch(loginUrl, {
method: 'post',
body: JSON.stringify(userLoginData),
credentials: 'include',
});
handleAuthResponse(response);
return response;
}
/** Call this when the user needs to send any authenticated request after already having logged in. */
export async function sendAuthenticatedRequest(
requestUrl: string,
requestInit: Omit<RequestInit, 'headers'> = {},
headers: Record<string, string> = {},
) {
const csrfToken = getCurrentCsrfToken();
if (!csrfToken) {
throw new Error('Not authenticated.');
}
const response = await fetch(requestUrl, {
...requestInit,
credentials: 'include',
headers: {
...headers,
[csrfTokenHeaderName]: csrfToken,
},
});
/**
* This indicates the user is no longer authorized and thus needs to login again. (This likely
* means that their session timed out or they clicked a "log out" button onr your website in
* another tab.)
*/
if (response.status === HttpStatus.Unauthorized) {
wipeCurrentCsrfToken();
throw new Error(`User no longer logged in.`);
} else {
return response;
}
}
/** Call this when the user explicitly clicks a "log out" button. */
export function logout() {
wipeCurrentCsrfToken();
}
All of these configurations must be set for the auth exports in this package to function properly:
csrfTokenHeaderName
(or just 'csrf-token'
) header via CORS headers with either of the following options:
customHeaders: [csrfTokenHeaderName]
in implementService
from @rest-vir/implement-service
.Access-Control-Allow-Headers
to (at least) csrfTokenHeaderName
.credentials: include
in all fetch requests on the client that need to use or set the auth cookie.Access-Control-Allow-Origin
(it cannot be *
).FAQs
Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.
We found that auth-vir demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.
Application Security
/Research
/Security News
Socket detected multiple compromised CrowdStrike npm packages, continuing the "Shai-Hulud" supply chain attack that has now impacted nearly 500 packages.