구글시트 웹앱 암호 보호 방법: 대시보드 5편
링크만 알면 누구나 들어오는 문제
구글시트 Apps Script로 만든 웹앱은 기본 설정이 상당히 개방적입니다. 배포 옵션을 "링크가 있는 모든 사용자"로 두면, 주소만 알면 누구나 접속해 화면을 볼 수 있습니다. 입고 예약 현황처럼 실제 운영 데이터가 올라가는 대시보드라면, 이 상태로 운영하기에는 부담이 큽니다.
이 글에서는 제가 물류센터에서 실제로 사용 중인 입고 예약 대시보드 앞단에 최소한의 잠금을 건 경험을 정리합니다. 주제는 하나입니다. 구글시트 웹앱 암호 보호 방법을 간단한 패스코드 방식으로 구현해, 링크가 퍼져도 아무나 대시보드에 접근하지 못하게 만드는 것입니다.
이 글은 구글시트 입고 예약 시스템 전체 구조: 왜 만들었고 어떤 순서로 이어지는가 에서 소개한 대시보드 갈래의 다섯 번째 편입니다. 앞선 글에서 일일 현황·상태별 현황·야드 현황을 보여주는 구글시트 대시보드와 웹앱 틀을 만든 상태에서, 이번 편에서는 그 화면에 웹앱 로그인에 가까운 패스코드 잠금 한 겹을 추가합니다.
왜 대시보드에 별도 잠금이 필요했는지
입고 예약 등록·체크인 같은 본 시스템은 구글 계정 권한만으로도 어느 정도 보호가 됩니다. 하지만 운영용 구글시트 대시보드는 상황이 다릅니다. 여러 팀이 동시에 보고, 외부 협력사에게도 링크만 전달해 상황을 공유하는 경우가 많습니다. 채팅방·메일에 URL을 여러 번 남기다 보면, 누가 언제 어디까지 전달했는지 추적하기 어려워집니다.
제가 일하는 현장에서도 입고 예약 대시보드를 모니터 여러 대에 띄워 두고, 도크 배정 팀·입고 검사 팀·운송사 담당자가 함께 봅니다. 인사 이동이나 협력사 변경이 있을 때마다 웹앱 배포 권한을 세밀하게 조정하는 것은 현실적으로 어렵습니다. 전체 WMS는 여전히 메인 시스템이지만, 예약·도착·도크 현황은 이 대시보드가 더 직관적이어서 자연스럽게 사용 범위가 넓어졌습니다.
그래서 다음과 같이 선을 그어 운영하고 있습니다.
- 예약·체크인 입력은 구글 계정 권한으로 제한합니다.
- 대시보드 웹앱은 링크 기반 접근이지만, 추가로 패스코드를 한 번 더 요구합니다.
여기서 다루는 패스코드는 완전한 의미의 로그인 시스템이 아닙니다. 구글 계정 기반 인증보다 보안 수준이 낮고, 브라우저 개발 도구를 다룰 줄 아는 사람에게는 완전한 방어가 되지 않습니다. 대신 "링크만 알면 누구나" 상태를 "패스코드를 아는 사람만" 수준으로 올리는 실무적인 타협안입니다. 사내 대시보드 보안 수준을 한 단계 올리고 싶을 때 쓸 수 있는 가벼운 구글시트 웹앱 접근 제한이라고 보시면 됩니다.
설계 방향: Script Properties에 패스코드를 숨기고 토큰으로 검증하기
구글시트 웹앱 암호 보호를 구현할 때 핵심은 두 가지였습니다.
첫째, 패스코드를 코드 안에 직접 쓰지 않는 것입니다. Apps Script 프로젝트는 여러 사람이 함께 볼 수 있고, 형상관리 저장소에 올리는 경우도 있습니다. 코드에 "warehouse2026" 같은 문자열을 그대로 적어 두면, 권한이 넓은 사람이라면 누구나 볼 수 있고, 나중에 코드가 다른 프로젝트로 복사될 때도 암호가 따라다닙니다. 이를 피하기 위해 Script Properties 비밀번호 저장 방식을 사용했습니다. 암호는 속성에만 저장하고, 코드에서는 키 이름만 아는 구조입니다.
둘째, 한 번 인증된 브라우저를 어떻게 기억할지입니다. 매번 페이지를 열 때마다 패스코드를 다시 물으면 운영자가 금방 피로해집니다. 그래서 서버에서는 패스코드가 맞으면 임시 토큰을 만들어 돌려주고, 브라우저는 그 토큰을 sessionStorage에 저장해 두었다가 이후 요청마다 함께 보냅니다. 서버 측 다른 함수는 이 토큰을 검증해 통과한 요청에만 데이터를 돌려줍니다. 이 흐름을 통해 간단한 형태의 구글시트 웹앱 로그인 구현을 흉내 낼 수 있습니다.
추가로, 틀린 암호를 무한히 빠르게 넣는 시도를 그대로 두고 싶지는 않았습니다. 다만 Apps Script 환경에서는 IP·브라우저 기준으로 강력한 차단을 구현하기 어렵습니다. 이 글에서는 틀렸을 때 Utilities.sleep(1500) 으로 응답을 약 1.5초 늦춰, 동일 사용자가 연속으로 시도할 때 속도를 조금 불편하게 만드는 정도의 방어선만 적용합니다. 병렬 호출이나 여러 기기에서 동시에 시도하는 공격까지 막아 주지는 않는다는 점을 분명히 해 두겠습니다.
전체 흐름: 패스코드 입력부터 데이터 제공까지
이번 글에서 구성하는 구글시트 웹앱 암호 보호의 전체 흐름은 다음과 같습니다.
- Script Properties에 운영용 패스코드를 한 번 저장합니다.
- 웹앱 HTML 초기화 시, 브라우저에 저장된 토큰이 없으면 패스코드 입력 폼을 보여 줍니다.
- 사용자가 패스코드를 입력하면
google.script.run.DASH_verifyPasscode()로 서버에 검증을 요청합니다. - 서버 함수
DASH_verifyPasscode()는
- 빈 값이면
EMPTY사유로 거부 - Script Properties에 저장된 값과 다르면 1.5초 대기 후
MISMATCH사유로 거부 - 일치하면 임의 토큰을 만들어 캐시에 저장한 뒤, 그 토큰을 클라이언트에 전달합니다.
- 클라이언트는 받은 토큰을
sessionStorage에 보관하고, 대시보드 초기화 함수에 토큰을 넘겨 호출합니다. - 서버의 데이터 제공 함수(예: 오늘 현황 조회, 야드 현황 조회)는 항상 인자로 받은 토큰을
DASH_isTokenValid_()로 검증한 뒤 통과한 요청에만 실제 데이터를 돌려줍니다. 토큰이 없거나 만료되면 데이터를 주지 않고 에러 메시지 또는 빈 결과를 돌려, 화면에서 다시 로그인 폼을 띄우게 할 수 있습니다.
이 구조를 맞추려면 서버 측과 클라이언트 측 코드가 정확히 연결되어 있어야 합니다. 아래 단계별 코드에서는 접두어를 모두 DASH_ 로 통일해, 다른 시트 자동화 시리즈 코드와 한 프로젝트 안에 섞여 있어도 이름 충돌이 나지 않도록 했습니다.
1단계 — Script Properties에서 패스코드 읽어오기
이 단계에서는 Script Properties 비밀번호 저장을 전제로, 저장된 패스코드를 읽어 오는 도우미 함수를 만듭니다. 암호가 없거나 너무 짧게 설정된 경우에는 바로 오류를 던져 개발 단계에서 실수를 잡아낼 수 있도록 했습니다.
1) 이 코드가 하는 일
- 스크립트 속성에서
DASH_SCM_PASSCODE키 값(패스코드)을 읽어 옵니다. - 값이 없거나 4자 미만이면 오류를 발생시켜 설정 실수를 바로 알립니다.
2) 붙여넣을 위치
- 구글시트 → 확장 프로그램 → Apps Script → 대시보드 프로젝트 →
Code.gs파일 맨 아래, 다른DASH_접두어 함수들 근처에 붙여넣습니다.
3) 붙여넣은 뒤 할 일
- 저장 후, 테스트 함수를 실행해 Script Properties에 값을 넣기 전·후의 동작을 각각 확인합니다.
const DASH_PASS_PROP_KEY = 'DASH_SCM_PASSCODE'; // → 패스코드 키 이름 상수
function DASH_getStoredPasscode_() { // → Script Properties에서 패스코드 읽기
const props = PropertiesService.getScriptProperties(); // → 스크립트 속성 서비스
const value = props.getProperty(DASH_PASS_PROP_KEY); // → 키로 값 읽기
if (!value) { // → 값이 비었으면
throw new Error('패스코드가 Script Properties에 설정되어 있지 않습니다.'); // → 오류 던지기
}
if (value.length < 4) { // → 너무 짧은 암호 방지
throw new Error('패스코드는 최소 4자 이상이어야 합니다.'); // → 설정 실수 방지
}
return value; // → 정상일 때 패스코드 반환
}
function DASH_testGetStoredPasscode() { // → 패스코드 읽기 테스트
const pass = DASH_getStoredPasscode_(); // → 함수 호출
Logger.log('Stored passcode length: ' + pass.length); // → 길이 로그
return pass.length; // → 길이 반환
}동작 확인 방법: 편집기에서 DASH_testGetStoredPasscode 를 실행합니다. Script Properties에 아직 값을 넣지 않았다면 오류 메시지가 떠야 하고, 값을 넣은 뒤에는 실행 로그에 길이가 숫자로 찍히면 정상입니다.
2단계 — Script Properties에 실제 패스코드 저장하기
이 단계는 코드 수정이 아닌 설정 작업입니다. 한 번만 제대로 해 두면, 이후에는 암호 교체 시에만 들어와 값을 바꾸면 됩니다.
1) 이 작업이 하는 일
- Script Properties에
DASH_SCM_PASSCODE라는 이름으로 실제 운영용 패스코드를 저장합니다. - 코드에서는 이 값을 직접 보지 못하고, 위에서 만든 도우미를 통해서만 읽습니다.
2) 설정 위치
- Apps Script 편집기 상단 톱니바 아이콘 클릭 → 프로젝트 설정 → 화면 하단의 스크립트 속성 영역에서 "스크립트 속성 열기" 버튼 클릭.
3) 저장 방법
- "속성 추가" 버튼을 눌러 다음처럼 입력합니다.
- 이름:
DASH_SCM_PASSCODE - 값: 운영에 사용할 패스코드 문자열 (예:
yardDash2026같은 예시 수치) - 저장 후 창을 닫고, 다시
DASH_testGetStoredPasscode를 실행해 길이가 제대로 읽히는지 확인합니다.
동작 확인 방법: 테스트 함수 실행 시 더 이상 "패스코드가 설정되어 있지 않습니다." 오류가 나지 않고, 로그에 길이가 8, 10과 같은 양의 정수로 찍히면 설정이 완료된 것입니다.
3단계 — Apps Script 패스코드 인증·토큰 발급 함수 만들기
이제 실제로 Apps Script 패스코드 인증을 수행하는 함수와, 토큰 생성·저장을 담당하는 도우미들을 구현합니다. 클라이언트에서는 이 함수를 google.script.run.DASH_verifyPasscode(...) 로 호출하게 됩니다.
1) 이 코드가 하는 일
- 입력된 패스코드를 정리한 뒤, Script Properties에서 읽어 온 값과 비교합니다.
- 빈 값이면
EMPTY, 틀린 값이면 1.5초 대기 후MISMATCH사유로 거부합니다. - 일치하면 랜덤 토큰을 생성해 캐시에 저장한 다음, 클라이언트에
ok: true와 함께 토큰을 돌려줍니다. - 다른 서버 함수가 토큰을 검증할 수 있도록
DASH_isTokenValid_()를 제공합니다.
2) 붙여넣을 위치
- 1단계 코드 바로 아래, 같은
Code.gs파일에 붙여넣습니다.
3) 붙여넣은 뒤 할 일
- 테스트 함수를 실행해 빈 값·틀린 값·맞는 값에 대한 응답 형식이 정확한지 확인합니다.
const DASH_TOKEN_CACHE_PREFIX = 'DASH_AUTH_'; // → 토큰 캐시 키 접두어
const DASH_TOKEN_TTL_SECONDS = 60 * 60; // → 토큰 유효기간(1시간)
function DASH_verifyPasscode(input) { // → 웹앱에서 호출하는 인증 함수
const cleaned = (input || '').trim(); // → 입력값 공백 제거
if (!cleaned) { // → 비었으면
return { ok: false, reason: 'EMPTY', message: '패스코드를 입력해 주세요.' }; // → 거부
}
const expected = DASH_getStoredPasscode_(); // → 저장된 패스코드 읽기
if (cleaned !== expected) { // → 값이 다르면
Utilities.sleep(1500); // → 1.5초 대기로 연속 시도 지연
return { ok: false, reason: 'MISMATCH', message: '패스코드가 올바르지 않습니다.' }; // → 거부
}
const token = DASH_generateAuthToken_(); // → 새 토큰 생성
DASH_storeAuthToken_(token); // → 캐시에 토큰 저장
return { ok: true, reason: 'OK', token: token }; // → 성공, 토큰 전달
}
function DASH_generateAuthToken_() { // → 랜덤 토큰 생성
const random = Utilities.getUuid(); // → UUID 생성
const raw = random.replace(/-/g, ''); // → 하이픈 제거
return raw.substring(0, 16); // → 앞 16자 사용
}
function DASH_storeAuthToken_(token) { // → 토큰을 캐시에 저장
const cache = CacheService.getScriptCache(); // → 스크립트 캐시
const key = DASH_TOKEN_CACHE_PREFIX + token; // → 캐시 키 만들기
cache.put(key, '1', DASH_TOKEN_TTL_SECONDS); // → 값과 TTL 저장
}
function DASH_isTokenValid_(token) { // → 토큰 유효성 검사
if (!token) { // → 값 없으면
return false; // → 무효
}
const cache = CacheService.getScriptCache(); // → 캐시 가져오기
const key = DASH_TOKEN_CACHE_PREFIX + token; // → 캐시 키
const value = cache.get(key); // → 값 읽기
return value === '1'; // → 값이 있으면 유효
}
function DASH_testVerifyScmPasscode() { // → 인증 함수 테스트
const expected = DASH_getStoredPasscode_(); // → 실제 패스코드 읽기
const cases = [
{ label: '빈 값', input: '' }, // → 케이스 1
{ label: '틀린 값', input: expected + 'x' }, // → 케이스 2
{ label: '맞는 값', input: expected }, // → 케이스 3
];
cases.forEach(function (c) { // → 각 케이스 실행
const res = DASH_verifyPasscode(c.input); // → 함수 호출
Logger.log(c.label + ': ' + JSON.stringify(res)); // → 결과 로그
});
return '테스트 완료'; // → 완료 표시
}동작 확인 방법: DASH_testVerifyScmPasscode 를 실행해 로그를 확인합니다. 세 케이스 각각에서 ok 값과 reason 값이 다른 형태로 나와야 합니다. 특히 맞는 값 케이스에서는 token 이 16자 문자열로 함께 출력되는지 확인합니다. 틀린 값일 때는 응답이 1.5초 정도 늦게 돌아오는 것도 눈으로 확인해 두면 좋습니다. 이 지연은 각 요청에만 적용되며, 동시에 여러 창에서 시도하는 경우를 막지는 못합니다.
4단계 — 웹앱 HTML에 패스코드 폼·토큰 저장 붙이기
이제 클라이언트 쪽에서 패스코드 입력·검증·토큰 저장을 연결합니다. 핵심은 "토큰이 없으면 폼만 보이고, 토큰이 있으면 바로 대시보드를 초기화" 하는 흐름입니다.
1) 이 코드가 하는 일
- 페이지 로드시 브라우저
sessionStorage에 토큰이 있는지 확인합니다. - 없으면 패스코드 입력 섹션을 보여 주고, 있으면 바로 대시보드를 초기화합니다.
- 사용자가 암호를 입력하면
DASH_verifyPasscode를 호출해 결과를 확인합니다. - 성공 시 토큰을 저장하고 대시보드를 초기화하며, 실패 시 메시지와 함께 입력을 다시 받을 수 있게 합니다.
2) 붙여넣을 위치
- Apps Script 프로젝트의 HTML 파일(예:
dashboard.html) 안<script>블록에 추가합니다. - 기존에 화면을 그리던 초기화 함수가 있다면, 아래
initDashboardWithToken안에 그 로직을 옮기거나 호출하도록 맞춥니다.
3) 붙여넣은 뒤 할 일
- 웹앱을 새 버전으로 배포한 뒤, 브라우저에서 빈 값·틀린 값·맞는 값 케이스를 직접 입력해 동작을 확인합니다.
<script>
const DASH_AUTH_TOKEN_KEY = 'DASH_AUTH_TOKEN'; // → 토큰 저장 키
function DASH_onDashboardLoad() { // → 페이지 로드 시 실행
const token = sessionStorage.getItem(DASH_AUTH_TOKEN_KEY); // → 기존 토큰 읽기
if (token) { // → 토큰이 있으면
initDashboardWithToken(token); // → 바로 대시보드 초기화
} else {
showPasscodeForm(); // → 암호 입력 폼 표시
}
}
function showPasscodeForm() { // → 암호 입력 화면 표시
document.getElementById('passcode-section').style.display = 'block'; // → 섹션 보이기
document.getElementById('dashboard-section').style.display = 'none'; // → 본문 숨기기
}
function submitPasscode() { // → 암호 전송 함수
const inputEl = document.getElementById('passcode-input'); // → 입력창 요소
const btnEl = document.getElementById('passcode-button'); // → 버튼 요소
const passcode = (inputEl.value || '').trim(); // → 값 읽기
btnEl.disabled = true; // → 중복 클릭 방지
btnEl.textContent = '확인 중...'; // → 진행 표시
google.script.run
.withSuccessHandler(function (res) { // → 성공 콜백
btnEl.disabled = false; // → 버튼 되돌리기
btnEl.textContent = '입력'; // → 텍스트 복원
if (!res || !res.ok) { // → 실패한 경우
alert(res && res.message ? res.message : '인증에 실패했습니다.'); // → 안내
inputEl.focus(); // → 다시 입력
return;
}
sessionStorage.setItem(DASH_AUTH_TOKEN_KEY, res.token); // → 토큰 저장
initDashboardWithToken(res.token); // → 대시보드 초기화
})
.withFailureHandler(function (err) { // → 서버 오류 콜백
btnEl.disabled = false; // → 버튼 되돌리기
btnEl.textContent = '입력'; // → 텍스트 복원
alert('서버 오류로 인증에 실패했습니다. 잠시 후 다시 시도해 주세요.'); // → 안내
console.error(err); // → 콘솔 로그
})
.DASH_verifyPasscode(passcode); // → 서버 함수 호출
}
function initDashboardWithToken(token) { // → 토큰으로 대시보드 초기화
document.getElementById('passcode-section').style.display = 'none'; // → 암호 폼 숨기기
document.getElementById('dashboard-section').style.display = 'block'; // → 본문 보이기
// 여기에 기존 대시보드 초기화 로직을 넣습니다. // → 기존 코드 위치
// 예: loadTodaySummary(token);
}
window.addEventListener('load', DASH_onDashboardLoad); // → 페이지 로드 이벤트 등록
</script>동작 확인 방법: 웹앱 URL을 시크릿 모드나 새 브라우저에서 열면, 처음에는 passcode-section 영역만 보여야 합니다. 빈 값을 제출하면 "패스코드를 입력해 주세요." 메시지와 함께 다시 입력을 요구해야 하고, 틀린 값을 넣으면 "패스코드가 올바르지 않습니다." 메시지가 떠야 합니다. 올바른 값을 넣은 뒤에는 대시보드 영역이 나타나야 합니다. 같은 브라우저에서 새로고침했을 때 추가 입력 없이 바로 대시보드가 뜨면 토큰 저장이 잘 된 것입니다.
5단계 — 데이터 제공 함수에서 토큰 검증하기
패스코드 인증과 토큰 발급만으로는 충분하지 않습니다. 실제 데이터를 돌려주는 서버 함수에서 토큰을 의무적으로 검사해야 웹앱 전체가 보호됩니다. 예를 들어 "오늘 도착 예약 현황"을 반환하는 함수가 있다고 가정해 보겠습니다.
1) 이 코드가 하는 일
- 클라이언트에서 넘어온 토큰을
DASH_isTokenValid_()로 확인합니다. - 유효하지 않으면 예외를 던지거나, 빈 데이터와 오류 사유를 함께 돌려줍니다.
- 유효할 때만 기존 로직대로 시트에서 데이터를 읽어 반환합니다.
2) 붙여넣을 위치
- 데이터를 내려보내는 기존 서버 함수(예:
DASH_getTodaySummary) 에 토큰 인자를 추가하고, 바로 앞부분에 토큰 검증 코드를 넣습니다.
3) 붙여넣은 뒤 할 일
- HTML 측에서 이 함수를 호출할 때도 토큰을 함께 전달하도록 수정합니다.
function DASH_getTodaySummary(token) { // → 오늘 현황 조회 함수
if (!DASH_isTokenValid_(token)) { // → 토큰 검증
throw new Error('인증되지 않은 요청입니다.'); // → 보호: 데이터 미제공
}
const sheet = SpreadsheetApp.getActive().getSheetByName('TODAY'); // → 예시 시트
const values = sheet.getDataRange().getValues(); // → 전체 데이터 읽기
// 이 아래는 기존 요약 계산 로직을 그대로 둡니다. // → 기존 로직
// 예시로 간단히 행 수만 돌려줍니다.
return {
totalRows: values.length, // → 전체 행 수
};
}동작 확인 방법: HTML에서 DASH_getTodaySummary 를 호출할 때 토큰을 넘기지 않고 실행해 보면, 콘솔 또는 Apps Script 로그에 "인증되지 않은 요청입니다." 오류가 떠야 합니다. initDashboardWithToken 안에서 올바른 토큰을 넘겨 호출했을 때만 정상 데이터가 내려오면, 구글시트 대시보드 보안이 최소한의 수준으로 연결된 것입니다.
실무 팁 — 이 구조를 현장에 적용할 때 유의할 점
실제 물류 현장에 이 구조를 적용하며 느낀 점을 몇 가지 정리합니다.
첫째, 암호 교체 절차를 미리 정해 두는 것이 좋습니다. 협력사 변경·담당자 이직 시기에 맞춰 분기 1회 정도 패스코드를 바꾸는 운영 규칙을 두면, 링크가 외부로 퍼졌더라도 어느 시점 이후에는 접속이 자연스럽게 끊깁니다. Script Properties에서 값만 바꾸면 되기 때문에, 코드 배포 없이도 교체가 가능합니다.
둘째, 토큰 저장 위치를 결정할 때 공용 PC인지, 개인 PC인지를 먼저 따져 보아야 합니다. 예시에서는 sessionStorage 를 사용해 브라우저를 완전히 닫으면 다시 로그인하도록 했습니다. 모니터에 상시 띄워 두는 대시보드라면 localStorage 로 바꾸고, 서버 쪽 TTL을 짧게 가져가는 방법도 있습니다. 다만 공용 PC에서 localStorage 를 쓰면 다른 사람이 브라우저를 열었을 때 바로 대시보드를 볼 수 있으므로, 환경에 맞게 선택해야 합니다.
셋째, 보안 기대치를 과도하게 높게 잡지 않는 것이 중요합니다. 이 방식은 어디까지나 사내 공유용 웹앱에 대해 "링크 유출 시 피해를 줄이는" 용도입니다. IP 기준 차단, 계정별 권한, 접속 로그 분석 등은 이 구조만으로 커버되지 않습니다. 민감도가 높은 데이터라면 웹앱 배포 자체를 조직 내 계정으로 제한하고, 패스코드 방식은 보조 수단으로만 사용하는 것이 안전합니다.
마지막으로, 토큰 검증을 도입하면 이후 대시보드 기능을 추가할 때마다 새 서버 함수의 첫 줄에 토큰 검사를 거는 습관을 들이는 것이 좋습니다. 실제로는 "한 함수쯤은 그냥 열어두자" 하다가 그 함수가 우회 경로가 되는 경우를 많이 보았습니다. 접속 구조를 단순하게 두고, 모든 데이터 제공 함수가 같은 DASH_isTokenValid_() 를 거치도록 통일하면 운영 중에도 관리가 수월합니다.
맺음말
구글시트 웹앱에 완전한 수준의 로그인 시스템을 얹는 것은 이 글에서 다룰 수 있는 범위를 벗어납니다. 대신 이번 글에서는 물류센터 입고 예약 대시보드에 실제로 적용해 본 경량 수준의 패스코드 기반 구글시트 웹앱 암호 보호 방법을 정리했습니다. Script Properties에 패스코드를 숨기고, Apps Script로 토큰을 발급·검증하며, HTML에서 토큰을 저장하고 전송하는 구조까지 연결하면, "링크만 있으면 누구나" 상태에서 실무에 쓸 만한 수준으로 한 단계 올릴 수 있습니다.
지금 바로 해 볼 수 있는 행동을 하나만 정리하겠습니다.
현재 사용 중인 대시보드 Apps Script 프로젝트를 열어 DASH_getStoredPasscode_() 와 DASH_verifyPasscode() 를 추가하고, Script Properties에 패스코드를 등록한 뒤, 빈 값·틀린 값·맞는 값 세 가지 케이스로 직접 테스트해 보시기 바랍니다. 이 과정을 한 번만 통과해 보면, 이후 다른 웹앱에도 같은 패턴으로 접근 제한을 빠르게 붙일 수 있을 것입니다.