Authentication
Stacknaut ships with two production-ready authentication methods: magic link email login and Google Sign-In. Both are fully implemented across the frontend, backend, and database — no auth library or third-party service needed beyond a standard email provider.
How It Works
Magic Link Login
Users enter their email and receive a sign-in link (and a 6-digit code as a fallback). Clicking the link or entering the code verifies their identity and creates a session.
The flow:
- User enters email on the login page
- Backend generates a cryptographic token and 6-digit code, stores both in the
magic_linkstable with a 15-minute expiry - An email is sent via Postmark with the magic link URL and code
- User clicks the link (or enters the code manually)
- Backend validates the token/code, marks it as used, and creates or retrieves the user
- A JWT session token is returned to the frontend
New users are automatically created on first login with a free trial. No separate registration step needed — the login is the signup.
Google Sign-In
One-click login using Google's identity platform. The frontend uses Google's Sign In With Google button, and the backend verifies the ID token server-side.
The flow:
- User clicks "Sign in with Google"
- Google returns an ID token to the frontend
- Frontend sends the ID token to the backend
- Backend verifies it using Google's OAuth2 library — no client secret needed, just the client ID
- User is created or retrieved, JWT token returned
Both methods feed into the same user table and session system. A user who first logs in with a magic link can later use Google (or vice versa) — they're matched by email.
Session Management
Sessions use JWT tokens signed by the backend:
- Tokens are stored in
localStorageon the frontend - Every authenticated request includes the token as a
Bearerheader - The backend verifies the token and looks up the user by email on each request via a
preHandlerhook - User data is cached locally; on session restore, the cache is used if available, otherwise the backend is called to refresh
There's no session table to manage. The JWT carries the user's email, and the backend validates it against the database on each request.
Database Schema
The auth system uses two tables:
users — the primary identity table. Email is the unique identifier. Tracks the authentication source (magicLink or googleLogin), subscription status, and trial credits.
magic_links — stores pending magic link tokens and codes. Each row has an email, token, code, expiry timestamp, and a used flag. Indexed on token, code, and email for fast lookups.
What You Get
- Magic link email login with both link and code verification
- Google Sign-In with server-side token verification
- JWT session tokens (no session store needed)
- Automatic user creation on first login (login = signup)
- Free trial provisioning for new users
- Email normalization (prevents duplicate accounts from case differences)
- Postmark integration for transactional email
- PostHog event tracking for login/logout analytics
- Telegram notifications for new signups (optional)
Key Files
backend/src/controllers/magicLinkController.ts — magic link send + verify
backend/src/controllers/googleAuthController.ts — Google token verification
backend/src/controllers/verifyJWTToken.ts — JWT verification preHandler
backend/src/routes/authRoutes.ts — auth route definitions
backend/src/routes/googleAuthRoutes.ts — Google auth routes
shared/src/schemas/magicLinks.ts — magic_links table schema
shared/src/schemas/users.ts — users table schema
frontend/src/stores/appStore.ts — login/logout/session logic
frontend/src/views/MagicLinkLoginView.vue — login page
frontend/src/views/MagicLinkVerifyView.vue — verification page
frontend/src/components/EmailLoginDialog.vue — email input dialog
Setup
Postmark (required for magic links): Create a Postmark account. Add
POSTMARKAPP_USERNAMEandPOSTMARKAPP_PASSWORDto your backend.env.Google Sign-In (optional): Create an OAuth 2.0 client ID in Google Cloud Console. Add
GOOGLE_CLIENT_IDto your backend.envandVITE_GOOGLE_CLIENT_IDto your frontend.env. No client secret is needed — verification uses only the client ID.JWT secret: Generate a random 64-character string for
JWT_SECRETin your backend.env. This signs all session tokens.
Both auth methods work independently at the code level. Google Sign-In doesn't require Postmark or any email setup. Note: the backend env validation currently requires all env vars to be present at startup, so you'll need to provide placeholder values for any method you're not using. You can use both from day one or start with one and add the other later.