How to hash and validate password in Deno using bcrypt

Last updated on

Hashing is cryptographic technique which converts the input block of text into hash form. There are various applications of it and one of them is storing passwords or api keys in hash form instead of plain form.

This technique helps to ensure that even if database gets exposed the attack won’t be able to read the password and use it for other purposes. There are various hashing algorithms but in this tutorial we are going to use bcrypt (it is based on blowfish).

In this step by step tutorial we will see how can we hash password in deno using bcrypt and then also how can we validate that password. This is basic requirement for any authentication system.

I will be using Hono for handling requests and response demonstration. You might already have HTTP server so just skip those part and just follow the password hashing part. Make sure you have deno installed. I am using 1.36.4 version of Deno.

Also the application can be deployed to Deno Deploy. This implementation won’t have ReferenceError: Worker is not defined on deno deploy because we will use synchronous version.

Step 1: Setting up Hono (Optional)

In your desired directory create index.ts file and import hono

(skip this step if you already have http server setup)

index.ts
import { Hono } from "https://deno.land/x/[email protected]/mod.ts";

const app = new Hono();

app.get("/", async (c) => {
  return c.json({ success: true });
});
Deno.serve(app.fetch);

Now run deno run —allow-net index.ts and go to localhost:8000

terminal
deno run --allow-net index.ts

Now add two routes. One to generate hash and one to verify hash and remove the get route. It was just for testing purpose.

index.ts
import { Hono } from "https://deno.land/x/[email protected]/mod.ts";

const app = new Hono();

app.post("/password/hash", async (c) => {
  const { password } = await c.req.json();
});

app.post("/password/verify", async (c) => {
  const { password, hashedPassword } = await c.req.json();
});

Deno.serve(app.fetch);

Step 2: Hashing password

Import these methods from bcrypt library.

import {
  hash as hashPromise,
  hashSync,
  compare as comparePromise,
  compareSync,
  genSaltSync,
} from "https://deno.land/x/bcrypt/mod.ts"

Now to make it work with the deno deploy add the following code.

const isRunningInDenoDeploy = (globalThis as any).Worker === undefined;

const hash: typeof hashPromise = isRunningInDenoDeploy
? (plaintext: string, salt: string | undefined = undefined) =>
new Promise((res) => res(hashSync(plaintext, salt)))
: hashPromise;

const compare: typeof comparePromise = isRunningInDenoDeploy
? (plaintext: string, hash: string) =>
new Promise((res) => res(compareSync(plaintext, hash)))
: comparePromise;

Now lets update the hash password route. Firstly call the generateSalt function then the hash function.

Here is the updated code.

index.ts
import { Hono } from "https://deno.land/x/[email protected]/mod.ts";
import {
  hash as hashPromise,
  hashSync,
  compare as comparePromise,
  compareSync,
  genSaltSync,
} from "https://deno.land/x/bcrypt/mod.ts";

const app = new Hono();

const isRunningInDenoDeploy = (globalThis as any).Worker === undefined;

const hash: typeof hashPromise = isRunningInDenoDeploy
  ? (plaintext: string, salt: string | undefined = undefined) =>
      new Promise((res) => res(hashSync(plaintext, salt)))
  : hashPromise;

const compare: typeof comparePromise = isRunningInDenoDeploy
  ? (plaintext: string, hash: string) =>
      new Promise((res) => res(compareSync(plaintext, hash)))
  : comparePromise;

app.post("/password/hash", async (c) => {
  const { password } = await c.req.json();

  try {
    const salt = await genSaltSync(8);
    const hashedPassword = await hash(password, salt);
    return c.json({ success: true, data: { hashedPassword: hashedPassword } });
  } catch (e) {
    console.log("error", e);
    return c.json({ success: false, data: null, error: e });
  }
});

app.post("/password/verify", async (c) => {
  const { password, hashedPassword } = await c.req.json();
});

Deno.serve(app.fetch);

Now we have the hash, you can store it in db. I am not covering that here. Although I am going to post an article on how to use deno kv to perform crud operation.

Step 3: Verify the hash

Now when user wants to login we need to verify the hash. So pass both password and password hash.

index.ts
import { Hono } from "https://deno.land/x/[email protected]/mod.ts";
import {
  hash as hashPromise,
  hashSync,
  compare as comparePromise,
  compareSync,
  genSaltSync,
} from "https://deno.land/x/bcrypt/mod.ts";

const app = new Hono();

const isRunningInDenoDeploy = (globalThis as any).Worker === undefined;

const hash: typeof hashPromise = isRunningInDenoDeploy
? (plaintext: string, salt: string | undefined = undefined) =>
new Promise((res) => res(hashSync(plaintext, salt)))
: hashPromise;

const compare: typeof comparePromise = isRunningInDenoDeploy
? (plaintext: string, hash: string) =>
new Promise((res) => res(compareSync(plaintext, hash)))
: comparePromise;

app.post("/password/hash", async (c) => {
  const { password } = await c.req.json();

  try {
    const salt = await genSaltSync(8);
    const hashedPassword = await hash(password, salt);
    return c.json({ success: true, data: { hashedPassword: hashedPassword } });
  } catch (e) {
    console.log("error", e);
    return c.json({ success: false, data: null, error: e });
  }
});

app.post("/password/verify", async (c) => {
  const { password, hashedPassword } = await c.req.json();

  try {
    const verifyPassword = await compare(password, hashedPassword);
    return c.json({ success: true, data: { verifyPassword } });
  } catch (e) {
    console.log("error", e);
    return c.json({ success: false, data: null, error: e });
  }
});

Deno.serve(app.fetch);

The password and hash is same so it provides verifyPassword as true

The password is different and so it provides verifyPassword as false

Step 4: Deploy

Push the code to github or even can use cli to deploy to deno deploy. I am using github. Here is the repo

After pushing the code go to https://dash.deno.com/projects and create a new project and choose your organisation then the repository and then select no build mode and then choose entry point as index.ts and then deploy.

And it will be deployed 🎉. Go to setting and you can change the domain. Congrats it now live. You can use it for authentication systems running on edge.

Conclusion

So now we have a working password hashing and verifying using bcrypt and deployed on deno deploy.

If you are facing any issue refer to the repo. Also you can reach me through EverythingCS discord server.