Skip to content

Instantly share code, notes, and snippets.

@potikanond
Last active October 18, 2025 08:17
Show Gist options
  • Save potikanond/919fcea4f2086b2b2fa24fefb1123444 to your computer and use it in GitHub Desktop.
Save potikanond/919fcea4f2086b2b2fa24fefb1123444 to your computer and use it in GitHub Desktop.
Lecture 19 - Full-Stack Web Application (Part 1)

Lecture 19 - Full-Stack Web Application (Part 1)

Content

  • Starter Repositories
  • Cross-Origin Resource Sharing (CORS)
  • Reverse Proxy
  • Password encryption using Bcrypt

Starter Repositories


Cross-Origin Resource Sharing (CORS)

cross-origin

  • Many web applications rely on 3rd-party resources from different domains
    • APIs, fonts, data or videos
  • Imagine that a web page from https://www.domain1.com uses JavaScript to fetch data from https://api.weather.com.
    • By default, the api.weather.com server does not allow requests from differnt origin or port
  • CORS allows your application to safely access resources on different domain
    • The api.weather.com server checks its CORS configuration if https://www.domain1.com is allowed.
    • Prevent malicious websites from sending requests to other sites without permission
  • Enables secure cross-domain data transfers by having the server explicitly permit requests

Allow CORS request in Express

Install the cores package

pnpm install cores
pnpm install -D @types/cores

Import and use cors middleware

import express from 'express';
import cors from 'cors';

const app = express();

// Enable CORS for all origins
app.use(cors());

// Your routes and other middleware go here
app.get('/api/data', (req, res) => {
    res.json({ message: 'This is CORS-enabled for all origins!' });
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

In a production environment, it is best practice to restrict CORS to only the specific orgins. We can achieve this by passing and options object with whitelist or origin property.

import cors from 'cors';

const allowedOrigins = ['http://localhost:3000', 'https://your-frontend-domain.com'];
const corsOptions: cors.CorsOptions = {
    origin: allowedOrigins
};

app.use(cors(corsOptions));

...

Reverse Proxy

  • An intermediary server that sits in front of one or more web (frontend or backend) servers.
  • Intercepting client requests and forwarding them to the appropriate server.
  • Enahnce security by hinding the identity of backend servers.

Configuring a Reverse Proxy in Vite

  • Vite has a built-in revere proxy functionality.
  • For an API service which are not designed to serves other external applications.

Vite Reverse Proxy

Open vite.config.js located in the root of your Vite project. Add server.proxy configuration. For example,

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      // Proxying API requests
      '/api': {
        target: 'http://localhost:3000',                // Your backend server address
        changeOrigin: true,                             // Changes the origin of the host header to the target URL
        rewrite: (path) => path.replace(/^\/api/, ''),  // Rewrites the path by removing '/api'
      },
      
      // You can add more proxy rules for other paths
      '/auth': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
    },
  },
});

Case #1:

  1. A frontend web application make an API request to the URL http://localhost:5173/api/v2/users.
  2. Vite's reverse proxy take a look at the request found that there is /api in the URL.
  3. Vite's reverse proxy then forwards the request to the URL http://localhost:3000/v2/users.
  • Note that the /api was removed from the URL.

Case #2:

  1. A frontend web application make an API request to the URL http://localhost:5173/auth/login.
  2. Vite's reverse proxy take a look at the request found that there is /auth in the URL.
  3. Vite's reverse proxy then forwards the request to the URL http://localhost:3001/auth/login.

Password Encryption using Bcrypt

Password Hashing

  • Bcrypt is an adaptive password-hashing function
    • Hashing converts input text into a unique fixed-length string (138-bit) that cannot be reversed
    • Salting, a unique random string (128-bit) is attached each string before hashing to protect against rainbow table attack
    • The higher adaptive cost parameter makes the process more computationally intensive and slow
  • Securely scrambles a plaintext password into unreadable string that is resistant to brute-force and rainbow table attacks

Play with Bcrypt

Bcrypt hash structure

Bcrypt hashing

Same password generates different hash

Salt and Hash

Bcrypt package

Install the bcrypt package

pnpm install bcrypt
pnpm install -D @types/bcrypt

We can now use bcrypt to a hashed password when a user create a new password. Create the hashPassword() function in src/utils/hash.ts file with the following code.

import Debug from "debug";
import bcrypt from "bcrypt";

const debug = Debug("app:hash");

export const hashPassword = async (plainPassword: string, saltRounds: number): Promise<string> => {
  // const salt = await bcrypt.genSalt(saltRounds);
  // const hashedPassword = await bcrypt.hash(plainPassword, salt);

  const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);

  return hashedPassword;
};

async function main() {
  for (let i = 1; i < 16; i++) {
    const hashedPassword = await hashPassword("1234", i);
    debug(`saltRounds=${i}, hash=${hashedPassword}`);
  }
}
main();

Next, create the comparePassword() function in src/utils/compare.ts file with the following code.

import Debug from "debug";
import bcrypt from "bcrypt";

const debug = Debug("app:compare");

export const comparePassword = async (password: string, hash: string): Promise<boolean> => {
  const isMatch = await bcrypt.compare(password, hash);
  return isMatch;
};

const hashes = [
  "$2b$04$eTJZhao.xVbwO0Nk6r0QR.rXidUh6Q7VQyOXEfOiI.f.1kWKr5GE.",
  "$2b$04$F0diMVVp8QayoVYbpAvlDulU7YHNtkD8Uj11zX3kRfcSYQXkf3g0C",
  "$2b$04$x/qMWRLQC7ZkLaIS81haw.6cP8hhipS1bFcs6BVP90NEUSkb1wl2a",
  "$2b$04$QfAO3rT.rpLg5rMLpVBqgedjekS.GLhT6Sp610myj0SEZLOOE0tj6",
  "$2b$05$427rfEYlN/76vMjck5n2bOUOM1rzL/FyIzn26PvyCxhsSE62fX.5G",
  "$2b$06$7tlphrVyFNgZv11q5zF2pexp7DReAnISEVGzN3ZLw/u.sCMiHuJcS",
  "$2b$07$i6EGvfaYbWBeecVya0bTK.7hOwy3kwMDiG1Ro4Eu362z7/z3L39sW",
  "$2b$08$X1HV7IRxckH.RgqG1jjIYOsRyCQMUiLoawaBfb05uzwQVDgwBVMX2",
  "$2b$09$8RTZjy80LBgrU0i4VRdGbuQQjleozkb7W47K7fqt64m9uM4PktRhS",
  "$2b$10$BuyRls5Tgok0JVFsPZiTU./TUrcGM3jHkTkf.3/Y.5ecY6d/NIJ8a",
  "$2b$11$Cqs1JJ660dzqgipEapnjF.gBeF2b9ONEDV3tyun2Rk/myW8K/yl3.",
  "$2b$12$zXDDP70MIfSsw9HysFcV9uj78i5pK6zWfFTPCvrEC4DBlKqeARh76",
  "$2b$13$dmzM28M74S8WlE9kzCrkWO5x9.M9YoXCAKBxHZvnZtvCPv2s99H9K",
  "$2b$14$GAdDkSvWT.sW4cgMNA5sd.npyWE5s8doMNS2lB4VxVtjJ6V3.4CYu",
  "$2b$15$iZTgg3w.dsuotW4JYTB3S.XhnOWzKPnOOW0NJVWs0nqAYidulJ5Ge",
];

async function main() {
  for (let hash of hashes) {
    const isMatch = await comparePassword("1234", hash);
    debug(isMatch);
  }
}
main();

Enable DEBUG information to print in console:

// For PowerShell
$env:DEBUG = "*" 

// For macOS
export DEBUG="*"

Now you can test the bcrypt hash and compare function with following commands.

npx tsx src/utils/hash.ts
npx tsx src/utils/compare.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment