사이트 암호화, 로그인 화면 만들기부터 자동 로그아웃까지

📝 한줄 요약

내 학습허브에 로그인 잠금을 걸고 싶어서 AI에 물었더니 처음엔 못생긴 브라우저 팝업이 뜨는 방식을 알려줬습니다. 예쁜 로그인 페이지로 바꾸고, 나중엔 자리 비우면 1시간 뒤 자동 로그아웃되고 브라우저 껐다 켜면 다시 로그인하게 만드는 것까지 갔습니다.

Before: 사이트가 그냥 열려있거나, 회색 브라우저 팝업으로 잠겨있음. 한 번 로그인하면 계속 로그인 상태.

After: 사이트와 톤이 맞는 예쁜 로그인 화면. 1시간 유휴면 자동 로그아웃, 브라우저 종료 시 세션 종료.

👥 이런 분께 추천

  • Vercel에 배포한 사이트에 나만 볼 수 있게 잠금을 걸고 싶은 분

  • Basic Auth 팝업이 너무 어색하다고 느끼는 분

  • 로그인 상태가 너무 오래 유지되는 게 찜찜한 분

  • 개발자가 아닌데 AI와 함께 사이트 보안을 스스로 만져보고 싶은 분

🙋 내 스펙 & 환경

항목

내용

직업

비개발자

코딩 경험

프론트엔드 대충 이해, 서버 쪽은 이번이 첫 경험

AI 도구

Claude Code

프로젝트

Astro 정적 사이트 + Vercel 배포

기존 상태

아무 잠금 없이 배포되어 있던 상태


진행 방법

1. AI에게 물어보니 Basic Auth 추천, 그런데 팝업이 못생김

사이트 보안이 신경 쓰이기 시작했습니다. 학습일지 페이지에 개인 기록이 있어서 남이 보면 곤란한데, 사이트 자체는 공개 배포되어 있었기 때문입니다.

AI에게 “내 학습허브에 로그인 잠금 걸고 싶어”라고 물었더니 Basic Auth를 추천해줬습니다. 방법도 쉽고, 파일 하나만 추가하면 된다고. 그대로 따라 했습니다.

배포하고 사이트에 접속해보니 정말 잠기긴 했는데, 로그인 창이 이렇게 뜹니다.

컴퓨터의 로그인 화면 스크린샷

내 사이트라는 느낌이 하나도 안 나요. 그냥 시스템 팝업.

AI에 다시 물었더니 이 팝업은 브라우저가 그리는 UI라서 사이트에서 커스터마이즈가 안 된다고 했습니다. 색·문구·이미지 다 못 바꿉니다.

2. 예쁜 로그인 화면 만들기: Vercel 환경변수 + 미들웨어

AI에게 “그러면 예쁜 로그인 페이지를 직접 만들려면 어떻게 해?”라고 물었습니다. 답은 이랬어요.

  • Vercel 환경변수: 아이디·비번·서명키를 사이트 코드가 아니라 Vercel 대시보드에 저장 (코드에 안 남음)

  • Vercel Edge 미들웨어: 사이트에 들어오는 모든 요청을 가로채서 로그인 여부를 확인하는 코드 한 파일

이 두 개만 있으면 로그인 페이지를 내가 만든 HTML로 대체할 수 있다고 했습니다.

셋팅 3단계로 진행했습니다.

1단계: Vercel 환경변수 3개 등록

Vercel 대시보드 → 프로젝트 → Settings → Environment Variables → Add Environment Variables 에서 아래 3개를 등록했습니다.

이름

뭐가 담기나

BASIC_AUTH_USER

로그인 아이디

BASIC_AUTH_PASS

로그인 비밀번호

AUTH_SECRET

로그인 상태를 위조 못 하게 하는 열쇠 (아무 랜덤 문자열 32자 이상)

AUTH_SECRET은 AI가 만들어준 명령으로 랜덤 문자열을 하나 뽑아 등록했습니다.

2단계: 미들웨어 파일 하나 추가

프로젝트 루트에 middleware.ts라는 파일을 하나 만들었습니다. 이 파일이 하는 일은 세 가지.

  • 로그인 안 된 사용자가 사이트를 열면 로그인 페이지로 보냄

  • 로그인 페이지에 아이디·비번 넣고 제출하면 검증하고 통과시켜줌

  • 로그인된 사용자는 그냥 사이트가 열림

이 파일 내용은 AI가 통째로 만들어줬어요. 저는 저장하고 커밋만 했습니다.

3단계: 커밋 → 푸시하면 Vercel 자동 배포

파일 저장하고 push하면 Vercel이 알아서 새 배포를 만들어줍니다. 1~2분 뒤 사이트에 접속하면 회색 팝업 대신 내가 만든 예쁜 로그인 페이지가 뜹니다.

한국어 로그인 폼 psd

3. 계속 로그인 상태여서 이상함, 알고 보니 30일 고정이었음

며칠 뒤 사이트를 다시 열었는데 로그인 화면 없이 바로 열립니다. 브라우저 껐다가 다시 켰는데도. 잠금이 아예 안 걸린 건가 싶어 시크릿 창으로 열어봤더니 시크릿 창은 로그인 페이지가 뜹니다.

AI에게 “브라우저 껐다 켜도 로그인이 유지되는데 왜 그런지 봐줘”라고 물었더니 원인을 찾아줬습니다.

셋팅할 때 로그인 유지 기간이 30일 고정으로 되어 있었어요. 한 번 로그인하면 30일간 계속 유지되고, 브라우저 종료해도 저장되어 있었습니다.

이건 편하긴 한데 위험합니다. 노트북을 잠깐 놓고 자리 비웠는데 옆에서 누가 열면 그대로 다 보일 수 있으니까요. 자리 비우면 자동으로 로그아웃되고, 브라우저 껐다 켜면 다시 로그인해야 하는 방식으로 바꾸기로 했습니다.

4. 1시간 자동 로그아웃 + 브라우저 종료 시 재로그인

미들웨어 파일에서 두 가지를 바꿨습니다.

바뀐 것 1: 1시간 유휴 만료

  • 로그인 후 아무 것도 안 하고 1시간 지나면 자동 로그아웃

  • 대신 사이트에서 뭔가 하고 있으면 만료 시간이 계속 갱신됨 (열심히 쓰는 중엔 안 잘림)

바뀐 것 2: 브라우저 종료 시 로그아웃

  • 로그인 상태 저장 방식을 “브라우저 닫으면 사라지는” 방식으로 바꿈

  • 브라우저 완전히 종료하면 다음에 열 때 다시 로그인 필요

이 두 가지를 미들웨어 파일에서 값 몇 개만 고치면 되는 거였어요. AI에게 요청하니 어디를 어떻게 바꿔야 할지 알려주고 바로 수정해줬습니다.

5. 고쳤는데도 여전히 유지됨. 진짜 원인 발견

미들웨어를 수정하고 배포한 뒤 크롬을 껐다 다시 열었습니다. 그런데 여전히 로그인 상태로 사이트가 그대로 열립니다. 뭔가 이상했습니다.

시크릿 창으로는 정상적으로 로그인 페이지가 뜨는 걸 보니 배포는 잘 된 것 같은데, 원래 창에선 왜 안 되는지 알아봤습니다.

개발자도구로 실제 쿠키 상태 확인:

  1. F12 → Application 탭 → 왼쪽 Cookies → 사이트 주소 클릭

  2. auth 쿠키의 Expires / Max-Age 열을 봤습니다

  3. 값이 Session이 아니라 **날짜(30일 뒤)**로 표시되어 있었어요

두 가지 원인이 겹쳐 있었습니다.

원인 A: 옛날에 심긴 지속 쿠키가 브라우저에 남아있음

  • 처음 로그인 상태 저장 방식을 바꾸기 전에 심긴 쿠키는 30일 지속용이었음

  • 미들웨어 정책을 바꿔도 서버는 브라우저에 이미 저장된 쿠키를 원격으로 지울 수 없음

  • 사용자가 사이트를 방문할 때마다 서버가 새 세션 쿠키로 덮어써줘야 정책 전환이 완료됨

원인 B: 미들웨어의 쿠키 갱신 코드에 버그

  • 사이트에 접속할 때마다 서버가 새 쿠키로 덮어써 주기로 되어 있었는데, 그 코드가 사실 브라우저까지 도달을 못 하고 있었음

  • 원인: next({ headers: { 'Set-Cookie': ... } }) 방식이 응답 헤더로 확실히 전달되지 않는 경우가 있음 (Vercel Edge 미들웨어 특성)

  • 그래서 옛 쿠키가 계속 살아있음

수정: 안전한 패턴으로 교체

// 이전 (전달 안 됨)
return next({ headers: { 'Set-Cookie': buildSessionCookie(exp, sig) } });

// 이후 (Vercel 문서 권장 패턴)
const response = next();
response.headers.append('Set-Cookie', buildSessionCookie(exp, sig));
return response;
복사

이 코드로 바꾸고 배포한 뒤, 브라우저에 있는 옛 쿠키를 한 번 지웠더니 (/logout 접속) 그다음부턴 정상 동작했습니다.

6. 검증

배포 후 확인했습니다.

  1. /logout으로 옛 쿠키 강제 삭제 → 로그인 페이지가 잘 뜸 ✅

  2. 아이디·비번 넣고 로그인 → 사이트 열림 ✅

  3. 개발자도구에서 auth 쿠키의 Expires 값이 **Session**으로 표시됨 ✅

  4. 브라우저 완전히 종료하고 다시 열기 → 로그인 페이지가 뜸 ✅

  5. 로그인한 채로 1시간 다른 일 하고 다시 새로고침 → 로그인 페이지로 튐 (예정대로) ✅

🛠 사용한 도구

도구

역할

Vercel 환경변수

아이디·비번·서명키를 코드에 안 남기고 저장

Vercel Edge 미들웨어

사이트 요청을 가로채서 로그인 여부 확인

Claude Code

미들웨어 파일 통째로 작성, 문제 진단, 자동 로그아웃 추가


배운 점

💡 핵심 교훈

1. AI가 처음 추천한 게 항상 최선은 아니다

Basic Auth는 확실히 쉽고 빠르지만 UI가 브라우저 팝업이라는 걸 처음엔 안 알려줬습니다. “이 방법 결과가 어떻게 보이는지”까지 물어봤어야 했어요. 결과를 확인하고 나서야 다른 방식으로 옮길 수 있었습니다.

2. 화면에 뜨는 UI가 브라우저 소관인지 사이트 소관인지 구분해야 한다

Basic Auth 팝업은 브라우저가 그립니다. 아무리 서버에서 예쁘게 만들려고 해도 안 됩니다. 화면을 마음대로 하려면 로그인 페이지 자체를 사이트가 반환해야 합니다. 이걸 알고 나니 왜 커스텀 로그인 페이지가 필요한지 이해가 됐습니다.

3. 로그인 유지 기간은 편한 만큼 위험하다

30일 유지는 편했지만 자리 비우면 위험합니다. 1시간 유휴 + 브라우저 종료 시 로그아웃이 저에겐 딱 맞았어요. 완전히 유휴가 아니라 활동 중이면 계속 유지되니 불편하지 않고, 자리 뜨거나 컴퓨터 재부팅하면 안전하게 잠깁니다.

4. 코드에는 비밀 안 넣기, 환경변수에

아이디·비번을 코드에 직접 쓰면 GitHub에 그대로 올라갑니다. 저장소가 PRIVATE이라도 언젠가 실수로 공개될 수 있어요. Vercel 환경변수는 대시보드에만 있고 코드나 GitHub에는 안 남습니다. 습관을 들여야 할 부분.

5. 정책 바뀌면 사용자가 이전 쿠키를 지워야 새 정책이 적용된다

30일 → 1시간으로 바꾼 뒤에도 크롬을 껐다 켜니 계속 로그인 상태였습니다. 알고 보니 옛날에 심긴 30일짜리 쿠키가 브라우저에 남아있어서 그거로 인정된 것. 개발자도구에서 옛 쿠키 지우고 새로 로그인해야 새 정책(세션 쿠키)이 적용됐습니다.

6. “코드는 배포됐는데 브라우저는 옛날 상태”라는 함정

미들웨어 코드에 이론적으로 옛 쿠키를 덮어써주는 로직이 있었지만, 실제론 그 코드가 브라우저까지 도달을 못 하고 있었어요. Vercel Edge 미들웨어에서 next({ headers: {...} })로 넘긴 Set-Cookie는 응답에 안 실릴 수 있습니다. const response = next(); response.headers.append('Set-Cookie', ...); return response;로 명시적으로 붙여야 확실합니다. 코드를 봐서 논리적으로 맞아 보여도, 실제로 브라우저의 쿠키를 봐서 확인해야 진짜 됐는지 알 수 있습니다.


적용할 점

🎯 결과

항목

Before

After

사이트 접근

아무나 열림

로그인 필수

로그인 화면

브라우저 회색 팝업 (못생김)

사이트와 톤 맞춘 예쁜 화면

로그인 유지

없음 → 나중엔 30일 고정 (위험)

활동하면 유지, 1시간 유휴면 자동 로그아웃

브라우저 종료 시

유지됨 (위험)

자동 로그아웃

아이디·비번 저장

코드에 하드코딩 위험

Vercel 환경변수에만

📋 복붙 가능한 프롬프트 (처음 만들 때부터 문제 없게)

Vercel에 배포한 사이트에 로그인 화면을 처음부터 붙일 때 쓰는 프롬프트입니다. 제가 겪은 함정들(Basic Auth 팝업, 30일 고정, Set-Cookie 미전달 버그)을 처음부터 안 겪게 명시했습니다.

이 Vercel 사이트에 커스텀 로그인 화면을 붙여줘. 조건과 주의사항을 정확히 지켜줘.

[요구사항]

1. 로그인 화면
   - 브라우저 기본 Basic Auth 팝업 절대 쓰지 말 것. 이유: UI 커스터마이즈 불가
   - 사이트와 톤이 맞는 HTML 로그인 페이지를 middleware가 직접 반환
   - 다크모드 자동 대응 (prefers-color-scheme)
   - 로그인 실패 시 401 + "아이디 또는 비밀번호가 맞지 않습니다" 표시
   - ?next=원래경로 파라미터로 로그인 후 원래 페이지로 돌려보내기 (open redirect 방지 필터 포함)

2. 자격 정보 저장
   - 아이디·비번·서명키는 반드시 Vercel 환경변수로 관리 (코드나 GitHub에 하드코딩 금지)
   - 환경변수 이름: BASIC_AUTH_USER, BASIC_AUTH_PASS, AUTH_SECRET
   - AUTH_SECRET 생성 명령도 알려주기:
     node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"

3. 로그인 유지 정책 (처음부터 이렇게 만들 것)
   - 30일 같은 고정 만료 쓰지 말 것
   - 대신 1시간 유휴 만료 (sliding window): 활동 있으면 매 요청마다 만료시각 갱신, 없으면 
     1시간 뒤 자동 로그아웃
   - 브라우저 완전 종료 시 로그아웃: Set-Cookie에서 Max-Age·Expires 모두 빼서 세션 쿠키로 발급

4. 세션 쿠키 서명
   - 쿠키 값에 만료시각과 HMAC 서명을 함께 담아 서버가 위조 검증 가능하게
   - HMAC 서명은 Web Crypto (crypto.subtle) 사용 (Vercel Edge에서 동작)
   - HttpOnly, Secure, SameSite=Lax 플래그 필수

5. /logout 처리
   - GET /logout 접속 시 clearCookie 헤더 (Max-Age=0)로 쿠키 삭제 후 /login으로 302

[★ 함정 회피 - 반드시 이 패턴 지킬 것]

인증된 요청을 통과시키면서 쿠키를 갱신할 때, 아래 잘못된 패턴은 절대 쓰지 말 것:

  // ❌ 나쁜 예: Set-Cookie가 브라우저까지 전달 안 되는 경우가 있음
  return next({ headers: { 'Set-Cookie': buildSessionCookie(newExp, newSig) } });

반드시 아래 안전한 패턴 사용:

  // ✅ 좋은 예: Vercel 문서 권장, 확실히 전달됨
  const response = next();
  response.headers.append('Set-Cookie', buildSessionCookie(newExp, newSig));
  return response;

이유: @vercel/edge의 next() 옵션 헤더는 응답 헤더로 확실히 전달되지 않는 케이스가 있어서,
sliding window 갱신 쿠키가 브라우저에 안 심어짐. 결과적으로 옛 쿠키가 브라우저에 계속 
살아있고, 세션 쿠키로 바꿨는데도 브라우저 껐다 켜도 로그인 유지되는 현상 발생.

[작업 순서]

1. 프로젝트 루트에 middleware.ts 생성
2. @vercel/edge 패키지 설치 (없으면): npm i @vercel/edge
3. 위 요구사항대로 middleware 작성 (특히 위의 안전한 쿠키 갱신 패턴 사용)
4. Vercel 대시보드에 환경변수 3개 등록 (BASIC_AUTH_USER, BASIC_AUTH_PASS, AUTH_SECRET)
5. 커밋·푸시 후 Vercel 자동 배포 대기
6. 배포된 URL에서 아래 curl 검증:
   - GET /            → 302 → /login?next=%2F
   - GET /login       → 200 + 로그인 HTML
   - POST /login (틀린 값) → 401
   - GET /logout      → 302 → /login (Set-Cookie: auth=; Max-Age=0)
7. 브라우저에서 실제 로그인 후 DevTools > Application > Cookies에서 auth 쿠키 확인:
   - Expires 컬럼이 반드시 "Session"으로 표시되어야 함 (날짜면 세션 쿠키 아님, 코드 재점검)
8. 크롬 완전 종료 후 재접속 → 로그인 화면 뜨는지 확인

[일반 주의]

- 미들웨어는 Vercel에서만 실행됨. 로컬 dev 서버에선 인증 안 걸림. 배포된 URL에서 최종 확인 필수.
- 정책 변경 후 사용자 브라우저에 남은 옛 쿠키는 서버가 원격 삭제 불가. 사용자에게 /logout 한 번 
  방문하거나 DevTools에서 쿠키 수동 삭제 안내 필요.
- 저장소가 public이면 middleware.ts 코드가 노출됨. 코드 자체에 비밀은 없지만 (env에만) 
  안전을 위해 저장소는 private 권장.

🔜 다음 계획

  1. 헤더에 “로그아웃” 버튼 넣기 (지금은 주소창에 /logout 직접 쳐야 함)

  2. 로그인 실패 여러 번이면 잠깐 잠금 (연속 로그인 시도 차단)

  3. 학습허브 외 다른 프로젝트에도 같은 방식 적용


솔직한 소감

처음 AI가 Basic Auth를 추천했을 때 그대로 따라 했다가 팝업 보고 좀 실망했습니다. 근데 그 실망 덕분에 “왜 이 팝업은 못 바꿀까?” 라는 질문이 나왔고, 그게 커스텀 로그인 페이지 만들기로 이어졌어요. 처음부터 완벽한 답을 받지 못한 게 오히려 더 배우게 만들었습니다.

자동 로그아웃 붙일 때도 그랬어요. 처음엔 로그인이 30일 유지되는 걸 몰랐고, 며칠 지나서 “왜 아직도 로그인 되어 있지?” 하고 알아채면서 다음 개선 지점이 나왔습니다. 한 번에 완성 안 하고 쓰면서 발견하고 고치는 과정이 오히려 자연스럽게 배움을 만들어줍니다.

기술 용어(HMAC, 쿠키 서명 같은 것)는 여전히 잘 몰라요. 하지만 “브라우저에 뜨는 게 뭐고, 그걸 누가 그리는지, 어디에 뭘 저장하는지”는 이해했습니다. 비개발자에게 필요한 이해는 이 정도인 것 같습니다.

물론 이 보안도 완벽하지는 않겠지만, 현재 제가 원하는 수준의 로그인 화면이 만들어진 것 같아요.

3
2개의 답글
밀어주고 끌어주는

온·오프라인 AI 스터디

AI로 어디까지 할 수 있는지
직접 확인하실 분만 신청하세요.