A team-built link-management SaaS where I owned the authentication system end to end — local login, Google OAuth, email verification, password reset, JWTs, and account linking. Built with NestJS, Next.js, Prisma, and PostgreSQL as part of a four-person Final Year Project.
February 2026 · Team Project · ~3 weeks, part-time (Feb 2026), plus a landing-page pass in May 2026
Authentication architecture for a team-built link-management SaaS. My contribution: I owned the authentication system end to end — local login, Google OAuth, email verification, password reset, JWT access and refresh tokens, role-based guards, and OAuth account linking. The GitHub Passport strategy exists but isn't wired into the frontend yet. The broader platform — short-link creation, custom domains, multi-tenant workspaces, click analytics, a link-in-bio builder, and ad-based monetization — was built by my teammates. I also built the standalone public marketing site, independent of the authentication work.
NanoLink needed an identity layer that could support multiple authentication paths without creating inconsistent or duplicate accounts. Users could register locally, return through Google or another OAuth provider, need to verify their email, reset a forgotten password, and access resources according to their role. The goal was to build authentication as a set of focused, reusable services rather than a single growing service tied directly to the rest of the application.
Frontend
Backend
Services
Solution
Replaced it with a 6-digit OTP with a 60-second expiry and a bounded resend budget (5 per 24 hours), and explicitly handled re-registration of an email that exists but was never verified.
Result
Shrinks the verification credential's live window from 24 hours to 60 seconds, while still tolerating a mistyped code through resends.
Authentication is split into distinct layers rather than handled by one service. Passport guards authenticate incoming requests, focused auth services contain business logic, and AuthRepository acts as the single Prisma boundary for persistence. OAuth providers enter through Passport strategies, while email verification and password reset use the SMTP layer independently.





Authentication flows become easier to reason about when each concern has a focused boundary. Separating tokens, OAuth, verification, password reset, and data access kept the module from turning into one large service.
OAuth is not just another login button. Account linking requires an explicit identity model so an existing local account and a later OAuth login do not accidentally become two users.
Short-lived verification credentials reduce the amount of time a leaked verification code remains useful. Moving from a 24-hour link to a 60-second OTP also introduced the need for controlled resend behavior.
A shared repository boundary makes a large authentication module easier to evolve because services do not need to know how Prisma models are accessed or mutated.
Working inside a larger team codebase taught me that ownership boundaries matter. The authentication system I built had to integrate with frontend and platform code that continued evolving outside my contribution.
Onboarding as a multi-step form — login, then create a workspace, then the rest of setup — works best as a sequence of independent, resumable steps rather than one long flow, since a user can drop off partway through and needs to pick back up without losing what they already completed.