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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mreal/digest-auth

v2.0.0

Published

Digest realization for client and server. All quality of protection (qop): auth, auth-int. Encryption algorithms: MD5, MD5-sess. Based on typescript

Downloads

1,167

Readme

Digest-Auth

Node.js CI Codecov

Version NPM Downloads

Node Current NPM Type Definitions NPM License

Features

  • Digest authentication implemented for both client and server.
  • Supports all qualities of protection (qop): auth, auth-int
  • Encryption algorithms supported: MD5, MD5-sess
  • Ability to force the use of specific qop and algorithm (to avoid certain vulnerabilities).
  • Uses cryptographic functions under the hood to generate random nonces.

Installation

npm i @mreal/digest-auth -S

Usage Client Digest Auth

  import {ClientDigestAuth} from '@mreal/digest-auth';

  const incomingDigest = ClientDigestAuth.analyze(headers['WWW-Authenticate']);
  const digest = ClientDigestAuth.generateUnprotected(incomingDigest, 'user', 'password', {
    method: 'POST',
    uri: '/some-uri'
  });

  console.log(digest.raw);
  // Digest username="user", realm="some-realm", nonce="some-nonce", uri="/some-uri", algorithm="MD5", response="48388ab4ca0c46a73e4d2f23ccc7632e"

The first step is to analyze the “WWW-Authenticate” header received from the server to parse, validate, and extract digest requirements.

As a result, we have an object of requirements, such as: nonce, realm, qop, etc.

This data can be utilized in its own logic and is necessary for the next step.

The second step generates a digest response based on the incomingDigest, credentials, and other payload involved in encryption.

As a result, we obtain an object with parts of the response (username, nonce, response hash), and a raw string for the “Authorization” request header for the HTTP request.

Additionally, this data can be utilized on its own in more complex implementations.

Quality of protection (qop) and algorithm

Client Digest Auth provides three methods to generate an Authorization header based on different qop values and without qop.

  import {ClientDigestAuth} from '@mreal/digest-auth';

  
  const digest = ClientDigestAuth.generateUnprotected(incomingDigest, 'user', 'password', {
    method: 'POST',
    uri: '/some-uri',
  });
  
  // OR
  
  const digest = ClientDigestAuth.generateProtectionAuth(incomingDigest, 'user', 'password', {
    method: 'POST',
    uri: '/some-uri',
    counter: 1,
  });
  
  // OR
  
  const digest = ClientDigestAuth.generateProtectionAuthInt(incomingDigest, 'user', 'password', {
    method: 'POST',
    uri: '/some-uri',
    counter: 1,
    entryBody: '{"a": "b"}'
  });

You can also enforce a specific algorithm when using qop.

  import {ClientDigestAuth, ALGORITHM_MD5_SESS} from '@mreal/digest-auth';
  
  const digest = ClientDigestAuth.generateProtectionAuth(incomingDigest, 'user', 'password', {
    method: 'POST',
    uri: '/some-uri',
    counter: 1,
    force_algorithm: ALGORITHM_MD5_SESS,
  });

By using these methods, you can enforce a specific qop and ignore the one requested by the server, protecting this aspect from a man-in-the-middle attack.

From a security standpoint, this package does not support automatic selection of the qop method based on the server’s response.

The standard says:
"A possible man-in-the-middle attack would be to add a weak authentication scheme to the set of choices, hoping that the client will use one that exposes the user's credentials (e.g., password). For this reason, the client SHOULD always use the strongest scheme that it understands from the choices offered."

rfc7616 / 5.8 / Man-in-the-Middle Attacks

Usage Serve Digest Auth

The first step involves analyzing the “Authorization” header received from the client to parse, validate, and extract the digest payload from the client.

As a result, we obtain an object containing payload elements such as nonce, cnonce, username, qop, etc.

This data can be used to extract the password and is necessary for the next step.

    import {ServerDigestAuth} from '@mreal/digest-auth';

    const incomingDigest = ServerDigestAuth.analyze(headers['Authorization'], false);
    
    console.log(incomingDigest); 
    // { username: 'user', response: 'e524170b3e02dedaf6a1110131fb5a50', nonce: 'd8483aa2fe3f31fe8b9497ed63e4899f3e352d980f7c56f0' ...

The second step performs verification through hash comparison.

To do this, we must provide a password and HTTP payload (other fields will be obtained from the incomingDigest).

As a result, we have a boolean value as status of verification.

    import {ServerDigestAuth} from '@mreal/digest-auth';

    const result = ServerDigestAuth.verifyByPassword(incomingDigest, password, { 
        method: 'POST', 
        uri: '/some-uri' 
    });
    
    console.log(result);
    // true

The last step involves generating a response.

This response contains a raw string for the “WWW-Authenticate” header for a 401 HTTP response and an object with the original data from the raw string.

You should generate a digest response and return a 401 “WWW-Authenticate” header in all cases except successful authentication (analysis error, validation error, verification error, user not found, etc.).

    import {ServerDigestAuth} from '@mreal/digest-auth';

    const response = ServerDigestAuth.generateResponse('all');
    
    console.log(response);
    // { realm: 'all', raw: 'Digest realm="all"...

Full example

    import {ServerDigestAuth} from '@mreal/digest-auth';
  
    try {
        const incomingDigest = ServerDigestAuth.analyze(headers['Authorization'], false);
        
        // Get the "password" that is stored somewhere on your server using "username", "realm"
      
        const result = ServerDigestAuth.verifyByPassword(incomingDigest, password, { 
            method: 'POST', 
            uri: '/some-uri' 
        });
        
        if (!result)
            throw new Error('authentication_error');
        
        // The request was authenticated successfully
          
    } catch (e) {
        const response = ServerDigestAuth.generateResponse('all');
        
        // Generate unauthorized response with 401 status code and “WWW-Authenticate” header from "raw" property
    }

Quality of protection (qop)

This option allows you:

  1. Protect against repeated requests by signing request data (body, links, method).

  2. Complicate the encryption algorithm

  3. Have rapid detection of hacking attempts

However, this requires the server to implement additional functions:

  1. Store the nonce temporarily after generation in any storage (memory, Redis, files, etc.).
  2. Implement your own validation by searching for the received nonce from a client among those nonces you have in storage.
  3. [Optional] Store the request counter in pairs with the nonce. Each successful request increments the request counter. Each request checks the counter provided by the client (nc) against the saved request counter in storage.
  4. [Optional] Error monitoring and alerts to detect hacking attempts.
    import {ServerDigestAuth, QOP_AUTH_INT} from '@mreal/digest-auth';
  
    try {
        const incomingDigest = ServerDigestAuth.analyze(headers['Authorization'], [QOP_AUTH_INT]);
        
        const result = ServerDigestAuth.verifyByPassword(incomingDigest, password, { 
            method: 'POST', 
            uri: '/some-uri', 
            entryBody: '',
        });
        
        if (!result)
            throw new Error('authentication_error');
      
        // Validation by searching for the received nonce from a client among those nonces you have in a storage might look something like this:
        if (!storage.hasNonce(incomingDigest.nonce))  
            throw new Error('unknown_nonce_error');
        
        // To check the counter provided by the client (nc) against the saved request counter in a storage might look something like this:    
        if (storage.getNonceCounter(incomingDigest.nonce) !== (incomingDigest.nc - 1))  
            throw new Error('incorrect_nc_error');
        
        // Storing a request counter in pairs with the nonce and incrementing the request counter on each successful request might look something like this:
        storage.incrementNonceCounter(incomingDigest.nonce);

        // The request was authenticated and post-action was performed successfully
    
    } catch(e) {
        // Error monitoring to detect hacking attempts can be placed here.
        
        const response = ServerDigestAuth.generateResponse('all', {
            opaque: 'customValue',
            qop: QOP_AUTH_INT,
            algorithm: ALGORITHM_MD5_SESS,
        });
        
        // Storing the nonce temporarily after the generation might look something like this:
        storage.addNonce(response.nonce);

        // Generate unauthorized response with 401 status code and “WWW-Authenticate” header from "raw" property
    }

An example with one time nonce (without using a counter).

This approach assumes that after each successful response there will be a 401 in order to generate a new "nonce", which will increase the load on the network and the server. This approach is not recommended unless you specifically want to achieve this.

    try {
        ....
        
        if (!result)
            throw new Error('authentication_error');
      
        // Validation by searching for the received nonce from a client among those nonces you have in a storage might look something like this:
        if (!storage.hasNonce(incomingDigest.nonce))  
            throw new Error('unknown_nonce_error');
        
        // Making the nonce invalid immediately after the first use might look something like this:
        storage.removeNonce(incomingDigest.nonce);

        // The request was authenticated and post-action was performed successfully
    
    } catch(e) {
        ...
        
        storage.addNonce(response.nonce);

        // Generate unauthorized response with 401 status code and “WWW-Authenticate” header from "raw" property
    }

Verify by secret

It allows you to use secrets (HA1) instead of passwords for Digest Authentication. This reduces the need to store the actual password in the database.

However, even though the password itself is not exposed, you must ensure the security of these secrets (HA1).

If A1 is compromised, an attacker could use it to generate valid digest responses without knowing the actual password.

Therefore, HA1 should be stored with the same security precautions as the password.

The standard says:

Digest authentication requires that the authenticating agent (usually the server) store some data derived from the user's name and password in a "password file" associated with a given realm. Normally this might contain pairs consisting of username and H(A1), where H(A1) is the digested value of the username, realm, and password as described above.

The security implications of this are that if this password file is compromised, then an attacker gains immediate access to documents on the server using this realm.

rfc7616 / 4.13 / Storing passwords

For convenience in creating this hash, you can use the HA1 helper.

  import { HA1 } from '@mreal/digest-auth';
  
  const secret = HA1.generate('username', 'realm', 'password');
  
  console.log(secret); // 4D86DBF27A98B2F451D973A00F567D6B

Next, use the verifyBySecret method instead of verifyByPassword.

  const result = ServerDigestAuth.verifyBySecret(incomingDigest, secret, { 
      method: 'POST', 
      uri: '/some-uri', 
      entryBody: '',
  });

recommendation to use "MD5-sess" algorithm for digest-auth.

Other

Multiple Authorization Header

Both server and client functions support analyzing multiple authorizations.

However, you must implement the business logic to choose the scheme that best suits your needs.

By default, without the “multiple authorization” option, the first found digest will be returned.

const multipleAuthorization = ServerDigestAuth.analyze(headers['Authorization'], [QOP_AUTH_INT], true);

console.log(multipleAuthorization) // Outputs: [ {scheme: 'Basic', raw: '....'}, { scheme: 'Digest', username="user", nonce="some-nonce", ...}]
const multipleAuthorization = ClientDigestAuth.analyze(headers['WWW-Authenticate'], true);

console.log(multipleAuthorization) // Outputs: [ {scheme: 'Basic', raw: '....'}, { scheme: 'Digest', username="user", nonce="some-nonce", ...}]