Software Engineer
Loading posts...

How I designed and implemented the bulk templated email delivery platform using React, SendGrid, MJML and NestJS for HackPSU organizers.

How we designed and built a comprehensive inventory tracking system for HackPSU using Next.js, React Query, and NestJS to manage thousands of items across multiple event locations.

How we implemented WebAuthn passkey authentication for HackPSU using Next.js and Firebase, creating a seamless biometric login experience that actually works in production.
Feel free to contact me at kanishksachdev@gmail.com

Here's the thing about running a hackathon with seven different web apps: authentication becomes a nightmare real quick. Every subdomain was doing its own thing, users were getting logged out constantly, and don't even get me started on the CORS errors (we'll get to those later).
So I did what any sane developer would do at: I decided to completely rewrite our entire auth system. Because that's always a good idea, right?
Here's what we desperately needed:
*.hackpsu.org subdomainsWhat I discovered over the next few sleepless weeks was a solution that actually worked, and I'm going to walk you through exactly how we built it, including the mistakes we made (spoiler: there were many).
You know what's fun? Managing authentication across seven different subdomains. Here's what we were dealing with:
hackpsu.org (main landing page)admin.hackpsu.org (our admin dashboard)emails.hackpsu.org (email campaign tool)sponsors.hackpsu.org (sponsor portal)inventory.hackpsu.org (inventory management)finance.hackpsu.org (finance tracking)checkin.hackpsu.org (event check-in system)Each app had its own Firebase client setup, its own redirect flows, and its own way of handling tokens. Users would log into the admin dashboard, then click over to the inventory system and surprise! They'd have to log in again. Every. Single. Time.
The worst part? Our organizers were constantly getting locked out during events because they'd lose their session jumping between apps. Picture trying to manage 500 hackers checking in while you can't access half your tools. Yeah, not great.
Here's where things got interesting: we needed one session cookie that would work across .hackpsu.org and all its subdomains, with proper HTTP-only and secure flags. No more localStorage token shenanigans, no more manual cookie juggling. Just seamless auth that actually worked.
After way too much coffee and reading Firebase docs until my eyes bled, we figured out our approach. Here's the flow we landed on (and trust me, this took several failed attempts to get right):
Here's how this actually works in practice:
idToken and POST it to our /api/sessionLogin endpoint.hackpsu.orgThe beautiful part? No more token management in our React components. No more localStorage. No more "why am I logged out again?" Just pure, simple cookie-based auth that works everywhere.
// /pages/api/sessionLogin.ts
import { NextRequest, NextResponse } from 'next/server'
import admin from '@/lib/firebaseAdmin'
import { serialize } from 'cookie'
export async function POST(req: NextRequest) {
const { idToken } = await req.json()
const expiresIn = 5 * 24 * 60 * 60 * 1000 // 5 days
const sessionCookie = await admin
.auth()
.createSessionCookie(idToken, { expiresIn })
const cookieHeader = serialize('__session', sessionCookie, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: expiresIn / 1000,
path: '/',
domain: '.hackpsu.org', // This is the key: works across all subdomains!
sameSite: 'strict',
})
const res = NextResponse.json({ status: 'ok' }, { status: 200 })
res.headers.set('Set-Cookie', cookieHeader)
return res
}
That domain: '.hackpsu.org' line? That's doing all the heavy lifting. Set it to the parent domain with a leading dot, and boom! Every subdomain gets access to this cookie. The HTTP-only and SameSite=strict flags are our security insurance policy (no XSS attacks reading our cookies, thank you very much).
// /pages/api/sessionLogout.ts
import { NextRequest, NextResponse } from 'next/server'
import admin from '@/lib/firebaseAdmin'
import { serialize } from 'cookie'
export async function POST(req: NextRequest) {
const cookie = req.cookies.get('__session')?.value
if (cookie) {
const decoded = await admin.auth().verifySessionCookie(cookie, true)
await admin.auth().revokeRefreshTokens(decoded.uid) // Nuclear option: kill all sessions
}
const expired = serialize('__session', '', {
maxAge: -1, // Time machine to the past
path: '/',
domain: '.hackpsu.org',
httpOnly: true,
sameSite: 'strict',
})
const res = NextResponse.json({ logout: true }, { status: 200 })
res.headers.append('Set-Cookie', expired)
return res
}
Here's something they don't teach you in CS classes: proper logout is harder than login. We're not just clearing the cookie, we're revoking all Firebase refresh tokens for that user across every device. That maxAge: -1 trick tells the browser to immediately expire the cookie. Clean slate, everywhere.
// /middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import admin from '@/lib/firebaseAdmin'
export async function middleware(req: NextRequest) {
if (req.nextUrl.pathname.startsWith('/api/protected')) {
const cookie = req.cookies.get('__session')?.value
try {
await admin.auth().verifySessionCookie(cookie || '', true)
return NextResponse.next() // You shall pass!
} catch {
return NextResponse.redirect('https://auth.hackpsu.org/login') // Nope.
}
}
return NextResponse.next()
}
export const config = {
matcher: '/api/protected/:path*',
}
This middleware is our bouncer. It sits at the door of every protected API route and checks IDs. No valid session cookie? You're getting redirected to the login page, no questions asked. The beautiful thing about Next.js middleware is it runs at the edge, so this check happens before your API route even gets called.
Pro tip: That verifySessionCookie(cookie, true) call? The true parameter checks for token revocation. We learned the hard way that you want this enabled, especially when dealing with role changes or emergency logouts.
// src/common/context/FirebaseProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react'
import {
signInWithEmailAndPassword,
onAuthStateChanged,
getIdToken,
} from 'firebase/auth'
import { auth } from '@/common/config/firebase'
const FirebaseContext = createContext(null)
export const FirebaseProvider = ({ children }) => {
const [user, setUser] = useState(null)
useEffect(() => {
const unsub = onAuthStateChanged(auth, async (u) => {
if (u) {
const idToken = await getIdToken(u)
// This is where the magic happens: sync the session cookie
await fetch('/api/sessionLogin', {
method: 'POST',
credentials: 'include', // Super important: includes cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken }),
})
setUser(u)
} else {
setUser(null)
}
})
return unsub
}, [])
const login = (email, pass) => signInWithEmailAndPassword(auth, email, pass)
return (
<FirebaseContext.Provider value={{ user, login }}>
{children}
</FirebaseContext.Provider>
)
}
export const useFirebase = () => useContext(FirebaseContext)
Here's the genius part: every time Firebase's auth state changes (login, logout, token refresh), we automatically sync our session cookie. The React components don't need to know anything about tokens or cookies. They just work with the user object like they always have.
That credentials: 'include' is crucial: it tells fetch to include cookies in the request. Without it, your beautiful session cookie just sits there unused.
You know what's cool about Firebase custom claims? We can store user roles directly in the token and read them during session verification. Every time someone hits a protected route, we get their permissions for free with no extra database calls needed.
For analytics, we just hook into the login/logout flows and track everything in our existing systems. Want to know how many admins logged in during the event? We've got you covered.
Let's talk about what actually matters in production:
Performance: Session verification is blazing fast. We're talking milliseconds. Firebase Auth uses indexed lookups, so even with thousands of concurrent users, response times stay consistent. Our login flow adds maybe 100ms overhead compared to the old token-juggling approach.
Security: This setup gave us defense in depth:
The best part? During our last hackathon (500+ attendees, 50+ organizers), we had zero auth-related issues. Zero. That's compared to the usual flood of "I can't log in" messages we used to get.
Here's the real impact of this system:
The organizers could finally jump between the inventory system, check-in app, and admin dashboard without constantly re-authenticating. Our sponsors could access their portal seamlessly. And I could sleep at night knowing our auth wasn't held together with localStorage and prayers.
We also moved our reset password flow to use the auth tooling. This was necessary because our old system (the one that was provided by Firebase) looked like this:

The new unified experience (shown in the cover image) is clean, works across all subdomains, and actually feels like a modern web app instead of a collection of disconnected services.
If you're building something similar, I'd love to hear how you're handling subdomain auth, especially if you've got ideas for scaling this to thousands of concurrent users. The token revocation piece still keeps me up at night sometimes, but hey, that's what makes it fun, right?