npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

auth-vir

v3.1.0

Published

Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.

Readme

auth-vir

Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers. ESM and browser friendly.

  • Reference docs: https://electrovir.github.io/auth-vir
  • Clone and run the demo: https://github.com/electrovir/auth-vir/blob/dev/packages/demo

Install

npm i auth-vir

Usage

Easy usage

For the easiest usage, construct and use BackendAuthClient on your server and FrontendAuthClient in your frontend.

Password hashing

  • 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.');
    }

Session auth

Host / server / backend side

Use this on your host / server / backend to authenticate client / frontend requests.

  1. Expose the AuthHeaderName.CsrfToken (or just 'csrf-token') header via CORS headers with either of the following options:
    1. Set customHeaders: [AuthHeaderName.CsrfToken] in implementService from @rest-vir/implement-service.
    2. Set the header Access-Control-Allow-Headers to (at least) AuthHeaderName.CsrfToken.
  2. Set the Access-Control-Allow-Origin header (it cannot be *) and properly implement CORS headers and responses.
  3. Generate JWT signing and encryption keys with one of the following:
    • Run npx auth-vir: the generated keys will be printed to your console.
    • Run await generateNewJwtKeys() (imported from this package) in your code.
  4. Securely store the generated keys in a secret place. Do not commit them. They should not be shared with anyone or any other host, client, or service.
  5. In your application code, load the string keys from step 1 into parseJwtKeys: await parseJwtKeys(stringKeys).
  6. Use the output of parseJwtKeys in all auth functionality:

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,
    type CsrfHeaderNameOption,
} from 'auth-vir';

type MyUserId = string;

/**
 * The CSRF header prefix for this app. Either `csrfHeaderPrefix` or `csrfHeaderName` must be
 * provided to all CSRF-related functions.
 */
const csrfOption: CsrfHeaderNameOption = {
    csrfHeaderPrefix: 'my-app',
};

/**
 * 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, csrfOption);
    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, csrfOption);
    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<MyUserId>(request.getHeaders(), jwtParams, csrfOption)
    )?.userId;
    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: MyUserId):
    | undefined
    | {
          id: MyUserId;
          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',
    };
}

Client / frontend side

Use this on your client / frontend for storing and sending session authorization.

  1. Send a login fetch request to your host / server / backend with {credentials: 'include'} set on the request.
  2. Pass the Response from step 1 into handleAuthResponse.
  3. In all subsequent fetch requests to the host / server / backend, set {credentials: 'include'} and include {headers: {[AuthHeaderName.CsrfToken]: getCurrentCsrfToken()}}.
  4. Upon user logout, call wipeCurrentCsrfToken()

Here's a full example of how to use all the client / frontend side auth functionality:

import {HttpStatus} from '@augment-vir/common';
import {
    type CsrfHeaderNameOption,
    getCurrentCsrfToken,
    handleAuthResponse,
    resolveCsrfHeaderName,
    wipeCurrentCsrfToken,
} from 'auth-vir';

/**
 * The CSRF header prefix for this app. Either `csrfHeaderPrefix` or `csrfHeaderName` must be
 * provided to all CSRF-related functions.
 */
const csrfOption: CsrfHeaderNameOption = {
    csrfHeaderPrefix: 'my-app',
};

/** 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(csrfOption).csrfToken) {
        throw new Error('Already logged in.');
    }

    const response = await fetch(loginUrl, {
        method: 'post',
        body: JSON.stringify(userLoginData),
        credentials: 'include',
    });

    handleAuthResponse(response, csrfOption);

    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(csrfOption);

    if (!csrfToken) {
        throw new Error('Not authenticated.');
    }

    const response = await fetch(requestUrl, {
        ...requestInit,
        credentials: 'include',
        headers: {
            ...headers,
            [resolveCsrfHeaderName(csrfOption)]: csrfToken.token,
        },
    });

    /**
     * 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(csrfOption);
        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(csrfOption);
}

Requirements

All of these configurations must be set for the auth exports in this package to function properly:

  • Expose the AuthHeaderName.CsrfToken (or just 'csrf-token') header via CORS headers with either of the following options:
    1. Set customHeaders: [AuthHeaderName.CsrfToken] in implementService from @rest-vir/implement-service.
    2. Set the header Access-Control-Allow-Headers to (at least) AuthHeaderName.CsrfToken.
  • Set credentials: include in all fetch requests on the client that need to use or set the auth cookie.
  • Server CORS should set Access-Control-Allow-Origin (it cannot be *).