---
title: "createHash"
description: "API reference for createHash — bcrypt password hashing and custom hash support."
---

> Documentation Index
> Fetch the complete documentation index at: https://ramonmalcolm10.github.io/ideal-auth/llms.txt
> Use this file to discover all available pages before exploring further.

# createHash

## Signature

```typescript
function createHash(config?: HashConfig): HashInstance
```

Creates a password hashing instance powered by bcrypt. Uses `bcryptjs` (pure JavaScript, edge runtime compatible) under the hood.

:::tip
Password hashing is only needed when you verify credentials yourself — specifically the Laravel-style `attempt()` flow with `hash` + `resolveUserByCredentials`. If you use `attemptUser`, `login(user)` directly, or authenticate via OAuth/external API, you don't need `createHash()` or `bcryptjs` at all.
:::

:::note
`bcryptjs` is an optional peer dependency. Install it only if you use `createHash()`:

```bash
bun add bcryptjs
```

If you prefer to use your runtime's built-in hashing (e.g., `Bun.password`) or a different algorithm (e.g., argon2), see [Custom HashInstance](#custom-hashinstance) below — no `bcryptjs` needed.
:::

```typescript
import { createHash } from 'ideal-auth';

const hash = createHash();
```

---

## HashConfig

```typescript
interface HashConfig {
  rounds?: number;
}
```

| Field | Type | Required | Default |
|---|---|---|---|
| `rounds` | `number` | No | `12` |

The `rounds` parameter controls the bcrypt cost factor. Higher values are more secure but slower. Each increment roughly doubles the computation time.

```typescript
const hash = createHash({ rounds: 14 });
```

---

## HashInstance

### make

```typescript
make(password: string): Promise<string>
```

Hashes a password using bcrypt and returns the hash string. Throws if the password is empty.

Each call produces a **different hash** because bcrypt generates a random salt every time.

```typescript
const hash = createHash();

const hashed = await hash.make('my-secure-password');
// "$2a$12$..."

// Each call produces a different result
const hashed2 = await hash.make('my-secure-password');
// "$2a$12$..." (different from hashed)
```

:::danger
`make()` throws an error if the password is an empty string. Always validate input before hashing.
:::

```typescript
try {
  await hash.make('');
} catch (error) {
  // Error: password must not be empty
}
```

### verify

```typescript
verify(password: string, hash: string): Promise<boolean>
```

Compares a plain-text password against a bcrypt hash. Returns `true` if they match, `false` otherwise.

```typescript
const hash = createHash();

const hashed = await hash.make('my-secure-password');

await hash.verify('my-secure-password', hashed); // true
await hash.verify('wrong-password', hashed);     // false
```

---

## Prehash behavior

Passwords longer than **72 UTF-8 bytes** are automatically SHA-256 prehashed before being passed to bcrypt. This is because bcrypt truncates input at 72 bytes — without prehashing, two passwords that share the same first 72 bytes would produce identical hashes.

The prehash is transparent. You do not need to do anything special; `make()` and `verify()` handle it automatically.

```typescript
const hash = createHash();

// This 100-character password exceeds 72 UTF-8 bytes.
// It is automatically SHA-256 prehashed before bcrypt processes it.
const longPassword = 'a'.repeat(100);
const hashed = await hash.make(longPassword);

// Verification works identically — prehash is applied automatically
await hash.verify(longPassword, hashed); // true
```

---

## Usage with createAuth

Pass the `HashInstance` to `createAuth` to enable the Laravel-style `attempt()` flow:

```typescript
import { createAuth, createHash } from 'ideal-auth';

const hash = createHash();

const auth = createAuth({
  secret: process.env.AUTH_SECRET!,
  cookie: cookieBridge,
  resolveUser: async (id) => db.user.findUnique({ where: { id } }),
  resolveUserByCredentials: async (creds) =>
db.user.findUnique({ where: { email: creds.email } }),
  hash,
});
```

---

## Registration example

Use `make()` to hash passwords before storing them in your database:

```typescript
import { createHash } from 'ideal-auth';

const hash = createHash();

async function registerUser(email: string, password: string) {
  const hashedPassword = await hash.make(password);

  const user = await db.user.create({
data: {
  email,
  password: hashedPassword,
},
  });

  return user;
}
```

---

## Custom HashInstance

You don't have to use `createHash()`. Any object that implements `HashInstance` works with `createAuth`. This lets you use your runtime's native hashing or a different algorithm without pulling in `bcryptjs`.

### Bun (bcrypt)

```typescript
import { prehash } from 'ideal-auth';
import type { HashInstance } from 'ideal-auth';

const hash: HashInstance = {
  make: (password) => Bun.password.hash(prehash(password), {
    algorithm: 'bcrypt',
    cost: 12,
  }),
  verify: (password, hash) => Bun.password.verify(prehash(password), hash),
};
```

Bun's native bcrypt is significantly faster than `bcryptjs` (native implementation vs. pure JavaScript). `prehash` applies SHA-256 for passwords exceeding bcrypt's 72-byte input limit, preventing silent truncation — the same protection that `createHash()` applies automatically.
### Bun (argon2id)

```typescript
import type { HashInstance } from 'ideal-auth';

const hash: HashInstance = {
  make: (password) => Bun.password.hash(password, {
    algorithm: 'argon2id',
    memoryCost: 65536, // 64 MB
    timeCost: 2,
  }),
  verify: (password, hash) => Bun.password.verify(password, hash),
};
```

Argon2id is the current [OWASP recommendation](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) for password hashing. It is resistant to both GPU and side-channel attacks.
### Node (argon2)

```typescript
import argon2 from 'argon2';
import type { HashInstance } from 'ideal-auth';

const hash: HashInstance = {
  make: (password) => argon2.hash(password),
  verify: (password, hash) => argon2.verify(hash, password),
};
```

Requires `bun add argon2` (native Node.js addon).

Pass your custom instance to `createAuth` the same way:

```typescript
const auth = createAuth({
  secret: process.env.AUTH_SECRET!,
  cookie: cookieBridge,
  resolveUser: async (id) => db.user.findUnique({ where: { id } }),
  resolveUserByCredentials: async (creds) =>
db.user.findUnique({ where: { email: creds.email } }),
  hash, // your custom HashInstance
});
```

:::caution
When using a custom hash, you are responsible for:
- **Cost factors** — choose appropriate memory/time costs for your algorithm
- **Algorithm choice** — bcrypt and argon2id are both production-ready; avoid MD5, SHA-1, or plain SHA-256
- **Input validation** — `createHash()` rejects empty passwords; your implementation should too
- **Prehash for bcrypt** — use `prehash()` from ideal-auth to prevent silent truncation at 72 bytes. Argon2 has no input length limit and does not need prehash.
:::

---

## Types

```typescript
import type { HashConfig, HashInstance } from 'ideal-auth';
```

Source: https://ramonmalcolm10.github.io/ideal-auth/api/create-hash/index.mdx
