본문 바로가기
Tech Notes

[앱인토스] `LOGIN_CODE_NOT_EXIST` 에러 처리하기

by miracle-tech 2026. 6. 22.
728x90
반응형

Apps in Toss LOGIN_CODE_NOT_EXIST 에러 처리하기: 사용자가 약관 동의를 닫았을 때의 올바른 로그인 UX

Apps in Toss 미니앱에서 토스 로그인을 붙이다 보면 appLogin() 호출 중 아래와 같은 에러를 만날 수 있습니다.

LOGIN_CODE_NOT_EXIST: 로그인을 완료할 수 없습니다.

처음 보면 “토스 로그인 연동이 실패한 건가?”, “서버에서 인가 코드 교환이 안 된 건가?”, “mTLS나 Supabase 설정 문제인가?”라고 생각하기 쉽습니다.

하지만 이 에러는 대부분 서버까지 가지도 않은 상태, 즉 토스 SDK의 appLogin() 단계에서 사용자가 로그인/약관 동의 화면을 닫았을 때 발생합니다.

이번 글에서는 LOGIN_CODE_NOT_EXIST가 정확히 어떤 상황에서 발생하는지, 그리고 앱에서는 어떻게 처리해야 안전한지 정리해보겠습니다.

3줄 요약

  • LOGIN_CODE_NOT_EXIST는 토스 appLogin()에서 authorizationCode를 받지 못했을 때 발생합니다.
  • 사용자가 토스 약관/동의 화면에서 닫기 또는 뒤로가기를 누르면 이 에러가 반환될 수 있습니다.
  • 이 경우 메인 화면으로 보내면 안 되고, 온보딩/로그인 CTA 화면에 그대로 남겨야 합니다.

토스 로그인 흐름 먼저 이해하기

Apps in Toss에서 토스 로그인은 보통 아래 흐름으로 진행됩니다.

1. 사용자가 미니앱 온보딩 화면에서 “토스로 시작하기” 버튼 클릭
2. 클라이언트가 Toss SDK appLogin() 호출
3. 토스가 로그인/약관 동의 화면 표시
4. 사용자가 약관 동의 완료
5. appLogin()이 authorizationCode 반환
6. 클라이언트가 authorizationCode를 서버 /api/auth/login으로 전달
7. 서버가 토스 API와 토큰 교환
8. 서버가 사용자 정보를 조회하고 앱 자체 JWT 발급
9. 클라이언트가 세션 저장 후 메인 화면 진입

중요한 점은 서버 로그인은 authorizationCode를 받은 뒤에야 시작된다는 것입니다.

즉, appLogin()이 실패해서 authorizationCode를 받지 못하면 /api/auth/login 서버 API는 호출되지 않습니다.

LOGIN_CODE_NOT_EXIST는 언제 발생할까?

토스 측 안내에 따르면, 사용자가 약관 동의 화면에서 동의하지 않고 닫기를 누르면 appLogin()에서 다음 에러가 전달될 수 있습니다.

error.code === 'LOGIN_CODE_NOT_EXIST'

실제 흐름은 다음과 같습니다.

토스로 시작하기 클릭
→ appLogin() 호출
→ 토스 약관/동의 화면 표시
→ 사용자가 닫기 또는 뒤로가기
→ authorizationCode 발급 안 됨
→ appLogin() Promise reject
→ error.code = LOGIN_CODE_NOT_EXIST

따라서 이 에러는 서버 장애가 아닙니다.

의심 항목 해당 여부
서버 /api/auth/login 실패 아님
mTLS 인증서 문제 아님
Supabase users upsert 실패 아님
JWT 발급 실패 아님
사용자가 로그인/약관 동의를 완료하지 않음 맞음

잘못된 처리: 닫기 후 메인 화면으로 보내기

사용자가 약관 동의를 하지 않았는데도 메인 화면으로 진입하면 안 됩니다.

잘못된 흐름은 다음과 같습니다.

토스로 시작하기 클릭
→ 약관 화면에서 닫기
→ LOGIN_CODE_NOT_EXIST 발생
→ 그런데 앱이 메인 화면으로 이동

이 경우 사용자는 실제로 로그인하지 않았고, 약관 동의도 완료하지 않았습니다.

그런데 앱이 로그인된 것처럼 동작하면 다음 문제가 생길 수 있습니다.

  • 로그인/약관 동의 우회처럼 보일 수 있음
  • 사용자 상태가 실제 서버 세션과 불일치
  • 이후 보호 API 호출에서 401 발생
  • 심사/QA에서 “동의하지 않았는데 서비스 진입 가능”으로 보일 수 있음

정상 동작은 반드시 아래와 같아야 합니다.

약관 화면에서 닫기
→ LOGIN_CODE_NOT_EXIST
→ 로그인 취소로 처리
→ 세션 저장하지 않음
→ 메인 화면 진입하지 않음
→ 온보딩 화면 유지

올바른 처리 방식

appLogin() 호출부 또는 로그인 핸들러에서 LOGIN_CODE_NOT_EXIST를 별도로 catch 해주면 됩니다.

예시는 다음과 같습니다.

function isLoginCodeNotExistError(error: unknown): boolean {
  if (!error || typeof error !== 'object') return false;

  const maybeError = error as {
    code?: unknown;
    message?: unknown;
  };

  return (
    maybeError.code === 'LOGIN_CODE_NOT_EXIST' ||
    (
      typeof maybeError.message === 'string' &&
      maybeError.message.includes('LOGIN_CODE_NOT_EXIST')
    )
  );
}

그리고 로그인 버튼 핸들러에서는 이렇게 처리합니다.

const handleLogin = async () => {
  setIsLoggingIn(true);
  setLoginError(null);

  try {
    const ok = await login();

    if (!ok) {
      setLoginError('로그인에 실패했어요. 잠시 후 다시 시도해주세요.');
    }
  } catch (error) {
    if (isLoginCodeNotExistError(error)) {
      setLoginError('약관 동의를 완료하면 서비스를 시작할 수 있어요.');
      return;
    }

    reportClientError('login', error);
    setLoginError('로그인 중 문제가 생겼어요. 잠시 후 다시 시도해주세요.');
  } finally {
    setIsLoggingIn(false);
  }
};

핵심은 다음 세 가지입니다.

  1. LOGIN_CODE_NOT_EXIST는 사용자 취소로 봅니다.
  2. 서버 장애처럼 Sentry error로 과도하게 기록하지 않습니다.
  3. 세션을 만들지 않고 온보딩 화면에 남깁니다.

사용자에게 어떤 문구를 보여주면 좋을까?

이 케이스는 “로그인 실패”라기보다 “로그인 미완료”에 가깝습니다.

따라서 너무 강한 에러 문구보다는 부드러운 안내가 좋습니다.

예시 문구:

약관 동의를 완료하면 서비스를 시작할 수 있어요.

또는:

로그인을 완료하지 않았어요. 다시 시작하려면 토스로 시작하기를 눌러주세요.

피하는 것이 좋은 문구:

로그인에 실패했습니다.
서버 설정을 확인해주세요.
오류가 발생했습니다.

이런 문구는 사용자가 직접 닫은 정상 취소 상황을 시스템 장애처럼 보이게 만들 수 있습니다.

기존 세션이 남아 있으면 헷갈릴 수 있다

QA 중에는 이런 상황도 자주 발생합니다.

토스 앱에서 서비스 연결 해제
→ 미니앱 재진입
→ 그런데 앱이 로그인된 것처럼 메인 화면 표시

이 경우는 LOGIN_CODE_NOT_EXIST와는 별도 문제입니다.

원인은 보통 미니앱 내부 Storage에 이전 auth session이 남아 있기 때문입니다.

토스 앱에서 연결을 끊었다고 해서 미니앱의 localStorage 또는 Apps in Toss Storage가 자동으로 삭제되는 것은 아닙니다.

그래서 앱 시작 시 저장된 세션이 있더라도 바로 홈으로 보내기보다, 서버에 한 번 검증하는 것이 안전합니다.

앱 시작
→ 저장된 auth session 복원
→ 바로 홈 렌더링하지 않음
→ /api/auth/me 호출
→ 200이면 홈 진입
→ 401 TOKEN_REVOKED면 세션 삭제 후 온보딩 표시

이렇게 하면 토스 연결 해제 후에도 앱이 로그인된 것처럼 보이는 문제를 줄일 수 있습니다.

자동 re-login도 주의해야 한다

또 하나 주의할 점은 보호 API 401 이후 자동으로 appLogin()을 다시 호출하는 구조입니다.

예를 들어 아래 흐름은 위험할 수 있습니다.

보호 API 호출
→ 401
→ refresh 실패
→ 자동 re-login
→ appLogin() 자동 호출

appLogin()은 사용자 액션 없이 호출하면 심사/UX 리스크가 생길 수 있습니다.

권장 흐름은 다음과 같습니다.

보호 API 호출
→ 401
→ refresh 실패
→ auth clear
→ 온보딩 표시
→ 사용자가 “토스로 시작하기” 버튼을 직접 누를 때 appLogin() 호출

즉, appLogin()은 반드시 사용자의 명시적인 CTA 클릭에서만 호출되도록 제한하는 것이 안전합니다.

QA 체크리스트

Apps in Toss 로그인 QA에서는 아래 항목을 꼭 확인하는 것이 좋습니다.

  • 앱 시작 직후 자동으로 appLogin()을 호출하지 않는다.
  • 온보딩/인트로 화면에서 사용자가 버튼을 눌렀을 때만 appLogin()을 호출한다.
  • 토스 약관/동의 화면에서 닫기 또는 뒤로가기를 누르면 LOGIN_CODE_NOT_EXIST를 catch한다.
  • LOGIN_CODE_NOT_EXIST 발생 시 메인 화면으로 진입하지 않는다.
  • LOGIN_CODE_NOT_EXIST 발생 시 온보딩/로그인 CTA 화면에 남는다.
  • 사용자에게 로그인 미완료 안내 문구를 보여준다.
  • 서버 /api/auth/login은 authorizationCode가 있을 때만 호출된다.
  • 토스 연결 해제 후 재진입 시 /api/auth/me 검증으로 TOKEN_REVOKED를 감지한다.
  • refresh 실패 후 자동 appLogin()을 호출하지 않는다.

마무리

LOGIN_CODE_NOT_EXIST는 처음 보면 치명적인 로그인 오류처럼 보일 수 있습니다.

하지만 실제로는 사용자가 토스 약관/동의 화면을 닫아 authorizationCode가 발급되지 않은, 비교적 정상적인 취소 흐름입니다.

따라서 이 에러를 서버 장애로 처리하기보다, 사용자 취소로 분리해서 처리하는 것이 좋습니다.

핵심은 간단합니다.

동의 완료 → authorizationCode 수신 → 서버 로그인 → 메인 진입
동의 취소 → LOGIN_CODE_NOT_EXIST → 온보딩 유지

이 기준만 지켜도 로그인 UX와 심사 안정성을 크게 높일 수 있습니다.

728x90