본문 바로가기
카테고리 없음

[AI 노동일지 5탄 #2] 게스트 로그인 시스템 — 익명 플레이 제거와 자동 계정 생성

by cocyio 2026. 3. 10.

원래는 익명으로 게임을 할 수 있었다. 토큰 없이 로컬에 데이터를 저장하고, 업적도 로컬에만 남았다. 그러니까 기기를 바꾸면 모든 게 사라졌다. 랭킹에 익명 유저가 매달렸다. 구조를 바꿔야 했다.

익명을 없애다

게임에 접속하면 SharedWallet이 초기화된다. 토큰이 없으면 자동으로 /api/auth/anonymous를 호출한다. 서버가 UUID를 생성하고, unsigned JWT를 발급한다.

// 서버 응답
{
  token: "eyJ...",  // alg: none
  userId: "484e95cc-458b-...",
  isAnonymous: true
}

이 토큰은 cocy_guest_token에 저장된다. 로그인 토큰(cocy_auth_token)과 완전히 분리된다. 로그인하면 게스트 토큰은 남겨두고, 로그아웃하면 게스트 토큰으로 복귀한다.

로그아웃 = location.reload()

로그아웃 후 상태를 초기화하는 데 비동기 체인이 꼬였다. SharedWallet이 로그아웃을 처리하고, 게임이 상태를 다시 읽고, UI가 갱신되고... 이 체인이 간헐적으로 깨졌다. 골드가 이상한 값으로 보이거나, 로그인 상태가 애매해졌다.

해결책은 단순했다. location.reload(). 로그아웃하면 페이지를 다시 로드한다. 게스트 토큰으로 복귀하고 리로드하니까 모든 상태가 깨끗하게 초기화된다. 복잡한 비동기 체인을 다 날렸다.

게스트도 서버 동기화

게스트 계정도 서버에 데이터를 저장한다. /api/user/data GET/PUT에서 anonymous 차단을 해제했다. 대신 랭킹 참여는 로그인 유저만 가능하다. isRegisteredUser()가 users 테이블에서 email 존재 여부를 확인한다.

이렇게 하면 게스트도 게임을 할 수 있고, 데이터도 날아가지 않는다. 그러다 로그인하면 랭킹에 이름을 올릴 수 있다. 자연스러운 전환이다.

getUserId()의 함정

문제가 하나 있었다. getUserId()가 JWT에서 ID를 가져와야 하는데, 이전 코드에서는 localStorage의 cocy_user_id를 먼저 확인했다. 이 값이 anon_ 형식이면 랭킹 API가 403을 반환했다. 로그인했는데도 랭킹이 안 쌍이는 버그.

수정: JWT 토큰을 우선으로 파싱하고, 거기서 sub 또는 userId를 추출한다. cocy_user_id는 펴백이지 마스터가 아니다.


다음 화: PvP 이펙트 오버홀 — Web Audio API로 전투 사운드 만들기

플레이: game.cocy.io/enhance