I already searched within Stack Overflow and in Google for solutions, but none of the solutions work, I always get following error :
Error: Module not found: Error: Can't resolve 'crypto' in ....
I did already:
npm update
ng update
auth.service.ts : ( not the html component - not in the browser - hidden authentication service )
import { Injectable } from '@angular/core';
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
const keyLength = 32;
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor() {};
async login( username: string, password: string ) {
if ( await AuthService.compare(username, await AuthService.hash(password))) {
localStorage.setItem('STATE', 'true');
return true;
}
return false;
}
async logout() {
localStorage.setItem('STATE', 'false');
return true;
}
isLoggedIn() {
const loggedIn = localStorage.getItem('STATE');
if (loggedIn == 'true')
return true;
else
return false;
}
/**
* Has a password or a secret with a password hashing algorithm (scrypt)
* @param {string} password
* @returns {string} The salt+hash
*/
static async hash( password: string ): Promise<string> {
return new Promise((resolve, reject) => {
// generate random 16 bytes long salt - recommended by NodeJS Docs
const salt = randomBytes(16).toString("hex");
scrypt(password, salt, keyLength, (err, derivedKey) => {
if (err) reject(err);
// derivedKey is of type Buffer
resolve(`${salt}.${derivedKey.toString("hex")}`);
});
});
};
/**
* Compare a plain text password with a salt+hash password
* @param {string} password The plain text password
* @param {string} hash The hash+salt to check against
* @returns {boolean}
*/
static async compare( password: string, hash: string ): Promise<boolean> {
return new Promise((resolve, reject) => {
const [salt, hashKey] = hash.split(".");
// we need to pass buffer values to timingSafeEqual
const hashKeyBuff = Buffer.from(hashKey, "hex");
scrypt(password, salt, keyLength, (err, derivedKey) => {
if (err) reject(err);
// compare the new supplied password with the hashed password using timeSafeEqual
resolve(timingSafeEqual(hashKeyBuff, derivedKey));
});
});
};
}
Crypto module is built-in in Node.js 22.4.1 which I use for my Angular 18: https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
The solution is not:
npm install crypto
(old way)You'll need to use something available in browsers like the Web Crypto API.
Hashing could look something like:
static async hash(password: string): Promise<string> {
const salt = crypto.getRandomValues(new Uint8Array(16))
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveBits"])
const derivedBits = await crypto.subtle.deriveBits({
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256"
}, key, 256)
return `${this.bufferToHex(salt)}.${this.bufferToHex(derivedBits)}`
}
Comparing could look something like:
static async compare(password: string, hash: string): Promise<boolean> {
const [saltHex, storedHashHex] = hash.split('.')
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveBits"]
)
const derivedBits = await crypto.subtle.deriveBits({
name: "PBKDF2",
salt: this.hexToBuffer(saltHex),
iterations: 100000,
hash: "SHA-256"
}, key, 256)
return storedHashHex === this.bufferToHex(derivedBits)
}
Shouldn't need to import
anything for this to work. I'm not a security expert so can't guarantee the security of these algorithms, but PBKDF2 is generally considered acceptable for password hashing.