Building a Personal Finance Tracker Part 3. Auth, Deployment, and Automation
Building a Personal Finance Tracker App Part 3 — Authentication, Deployment, Automation
> We've confirmed the app works. Now it's time to release it to the world. This includes authentication to protect financial data, deployment to Vercel, and a pipeline that automatically deploys with just a GitHub push.
Why Authentication is Necessary
Deploying to Vercel generates a URL. The problem is that anyone who knows that URL can see my financial data. Hwing household ledger data, portfolio holdings, net worth trends — all exposed with a single API call.
Since it's a single-user app, a complex authentication system isn't needed. OAuth, JWT, session management — all overkill. A single Bearer token is sufficient.
Implementing Bearer Token Authentication
Authentication Flow
1. 앱 접속 → localStorage에 토큰 없으면 로그인 화면
2. 비밀번호 입력 → POST /api/auth로 검증
3. 서버에서 환경변수의 비밀번호와 비교
4. 일치 → 토큰을 localStorage에 저장 → 앱 진입
5. 이후 모든 API 요청에 Authorization: Bearer <token> 포함
6. 서버에서 토큰 불일치 → 401 Unauthorized
The core is simple. Set a secret password in an environment variable and compare it to the value sent by the client. If it matches, use that password itself as the token. There's no reason to build a separate token issuance server.
Scope of Protection
Not all APIs need protection.
| Endpoint | Protection | Reason |
|---|---|---|
/api/data |
Y | Portfolio, financial data |
/api/whooing |
Y | Personal household ledger |
/api/exchange-rate |
N | Public data, no sensitive info |
/api/auth |
- | Authentication endpoint itself |
Public data like exchange rates requires no protection. Only endpoints accessing personal financial data need protection.
Server-Side Implementation
Validate tokens at the very top of each Vercel Function requiring protection.
요청 수신
→ Authorization 헤더에서 Bearer 토큰 추출
→ 환경변수의 비밀번호와 비교
→ 불일치 → 401 반환
→ 일치 → 원래 로직 실행
Apply this pattern identically to /api/data and /api/whooing. While middleware could be created, since Vercel Functions are independent serverless functions, placing the validation code within each function is clearer.
Client-Side Implementation
Login Screen. If no token exists in localStorage, render the login screen instead of the app. When a password is entered, send a POST request to /api/auth. If successful, store the token.
Automatic header injection. Create an authHeaders() function in src/lib/auth.ts { Authorization: 'Bearer <token>' }. Include this header in every fetch call to the server API.
// 모든 보호된 API 호출에 공통 적용
fetch('/api/data', {
headers: { ...authHeaders() }
})
syncToServer() and syncFromServer() in storage.ts, whooingFetch() in whooing.ts — Add authHeaders() to all functions calling the server.
Authentication in Development
It's cumbersome to see the login screen during local development. Adding a /api/auth endpoint to the Vite mock that passes any password, or storing the token in the catch block of the login component even when API connection fails, makes development more convenient.
Vercel Deployment
Pre-Deployment Checks
Verify these items before deploying.
Check .gitignore. Ensure .env.local (API keys, passwords), .vercel/ (project settings), node_modules/, etc., are all included. Especially, .env.local must never be committed.
Configure vercel.json. Since it's an SPA, all paths must route to index.html. However, the /api/* path should be sent to Vercel Functions.
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "/api/$1" },
{ "source": "/(.*)", "destination": "/index.html" }
]
}
Deployment Process
# 1. Vercel CLI 설치
npm install -g vercel
# 2. 로그인
vercel login
# 3. 프로젝트 연결
vercel link --yes
# 4. 환경변수 설정 (프로덕션)
printf '%s' '값' | vercel env add 변수명 production
# 5. 배포
vercel --prod
Using printf '%s' in environment variable setup is crucial. Using echo appends a newline (\n) to the value's end, causing errors like invalid header value during API calls. Spent quite some time troubleshooting this.
Required Environment Variables
| Variable | Description |
|---|---|
WHOOING_APP_ID |
Whooing App ID |
WHOOING_TOKEN |
Whooing OAuth Token |
WHOOING_SIGNATURE |
Whooing OAuth Signature |
POMME_SECRET |
App Secret (Bearer Token) |
KV_REST_API_URL |
Upstash Redis URL (Automatic when creating KV store) |
KV_REST_API_TOKEN |
Upstash Redis Token (Automatic) |
Whooing/authentication-related variables must be added manually. KV-related variables are automatically connected when creating an Upstash Redis store via the Vercel dashboard.
KV Store Setup
Select Vercel Dashboard → Storage → Create → Upstash Redis to create a KV store. Five environment variables (KV_REST_API_URL, KV_REST_API_TOKEN, etc.) will automatically connect to your project. You can use them directly from the @vercel/kv package without additional setup.
Troubleshooting
Git author error. Running vercel --prod may trigger a "Git author must have access to the team" error. This occurs if your email isn't set in git config or differs from your Vercel account email. Resolve by setting your GitHub email via git config user.email.
Environment variable line breaks. As mentioned earlier, but worth emphasizing again: Do not use echo "value" | vercel env add. You must use printf '%s' 'value' | vercel env add. If a line break is included in the value, it causes errors during HTTP header parsing, which are extremely difficult to trace.
GitHub Integration — Automated Deployment
We confirmed manual deployment works with vercel --prod. However, running the CLI every time you modify code is cumbersome. We need to set it up to deploy automatically upon pushing to GitHub.
Creating the GitHub Repo
# private 레포 생성
gh repo create pomme --private
# 첫 push
git push -u origin main
Since it's a personal finance app, it's obviously a private repo.
Vercel-GitHub Integration
Here, I hit a wall. While there's a vercel git connect CLI command, accessing a private repo requires granting the Vercel GitHub App permission to that repo. The CLI alone isn't sufficient.
How to connect via the Vercel Dashboard
- vercel.com → Project → Settings → Git
- Click "Connect Git Repository"
- Select GitHub → Install/Grant permissions for the Vercel GitHub App
- Select the repository → Save
During this process, a popup appears to grant the GitHub App access to the repository. You can selectively grant permissions for specific repositories, so you don't need to grant access to all repositories.
Verifying Automatic Deployment
Once connected, it works like this:
git add . && git commit -m "메시지" && git push origin main
│
▼
GitHub webhook 발생
│
▼
Vercel 빌드 트리거
│
▼
약 20초 후 프로덕션 배포 완료
After testing by changing the version in package.json and pushing, deployment completed in just 20 seconds. Now, code modification → push → deployment flows seamlessly as one continuous process.
Connecting a Custom Domain
The URL automatically generated by Vercel (project-name-xxx.vercel.app) is long and hard to remember. Connecting a subdomain from a domain you already own is much cleaner.
Subdomain Approach
The main domain (byminseok.com) is already used for a blog on GitHub Pages. Splitting by path (like /lab) isn't possible because GitHub Pages doesn't support proxies. Instead, using a subdomain is simple.
byminseok.com → GitHub Pages (블로그)
pomme.byminseok.com → Vercel (재무 추적 앱)
It's under the same domain but routes to a completely separate server. Just one DNS record is needed.
Setup Method
Step 1 — Add a CNAME Record to DNS
Add it in the DNS management section of your domain registrar (Porkbun, Cloudflare, Gabia, etc.).
Host: pomme
Type: CNAME
Value: cname.vercel-dns.com
Step 2 — Add Domain to Vercel
vercel domains add pomme.byminseok.com
That's it. Vercel automatically issues an SSL certificate and connects the domain to your production deployment. You can access https://pomme.byminseok.com within 1-2 minutes.
Final Architecture
Combining all components from Parts 1-3 results in this structure.
[개발]
코드 수정 → git push origin main
[배포]
GitHub → Vercel 자동 빌드 → 프로덕션 배포 (20초)
[런타임]
브라우저
├── 로그인 → POST /api/auth → Bearer 토큰 발급
├── 대시보드 → GET /api/whooing (후잉 프록시)
│ → GET /api/exchange-rate (환율)
│ → GET finnhub.io (주식 시세, 직접 호출)
├── 데이터 저장 → localStorage (즉시)
│ → POST /api/data (1초 debounce, KV 동기화)
└── 앱 시작 → GET /api/data (KV에서 pull)
Vercel Functions
├── /api/auth ← 비밀번호 검증
├── /api/whooing ← 후잉 프록시 (Bearer 보호)
├── /api/exchange-rate ← 환율 프록시 (6시간 캐시)
└── /api/data ← KV 읽기/쓰기 (Bearer 보호)
Vercel KV (Upstash Redis)
└── pomme_data ← 전체 앱 데이터 한 덩어리
Series Wrap-Up
This three-part series detailed building a financial tracking app from scratch to deployment.
Part 1 — Establishing financial principles, app concept (independence tracker), API research, architecture design, data model
Part 2 — Frontend implementation (gauges, portfolio, charts), external API integrations (Whooing, Finnhub, exchange rates), dark mode, responsiveness, KV sync
Part 3 — Bearer token authentication, Vercel deployment, GitHub auto-deployment, custom domain
Looking Back
This app took two days from planning to deployment. I built it with Claude Code, and the biggest advantage of an AI coding assistant isn't speed, but lowering the barrier to "just try building it." By delegating tasks like API documentation analysis, boilerplate generation, and CSS tweaks, I could focus solely on "what to build" and "why build it."
Building Apps as a Non-Developer
The simplicity of the tech stack made this possible for someone without a professional development background. One React SPA, three serverless functions, one Redis key. You can build practical apps without complex backends.
What matters isn't the tech, but first figuring out "what I need". Only then does technology become a tool to fulfill that need. Establishing financial principles first ultimately shaped the app's direction.
Looking Ahead
There are still things left to do. Migrating from @vercel/kv to @upstash/redis, optimizing bundle size, and so on. But even in its current state, it's perfectly sufficient for daily use. The rest can be tackled as needed while using it.
The purpose of this project is to open the app and see the gauge gradually rise. And that's working well.</token>