[PonsLink] 계정 없이 링크 하나로 만나는 방을 만들고 싶었다
처음 PonsLink를 붙잡았을 때 내가 원했던 장면은 거창하지 않았다. 링크 하나만 던지면 상대가 별도 계정 없이 들어와서, 필요한 이야기를 나누면 끝나는 방. 회원가입 장벽이 만남의 속도를 늦추는 걸 보고 싶지 않았다.
그런데 계정 없는 링크를 "그냥 열린 방"으로 두면 금방 무너진다. 누구나 들어올 수 있으면, 권한·보관·악용 방지가 전부 제품 책임으로 남는다. 나는 이 지점에서 링크의 개방성과 권한의 명확함을 한덤어리로 푸는 착각을 했다.

열고 보니 링크 하나가 끝이 아니었다
링크를 클릭한 뒤 사용자가 겪는 상태는 한 가지가 아니다.
- 방이 이미 닫혔을 수도 있다
- host가 아직 준비되지 않았을 수도 있다
- 요청은 남아 있지만 결제나 승인 상태가 확정되지 않았을 수도 있다
- 게스트 identity는 있지만 세션이 만료됐을 수도 있다
"링크 클릭 = 입장"으로 그리면, 이 중 하나라도 어긋날 때 사용자는 그냥 멈춘다. 멈춤을 "연결 실패"로 뭉개면 진짜 원인은 가려진다. 작은 실패가 전체 경험으로 읽히는 지점이 여기서도 반복된다.

처음에는 링크에 모든 걸 담으려 했다
초기 설계는 단순했다. public link가 있으면 그 안에서 다 해결되는 줄 알았다. guest identity, room 권한, feature 제한, TURN 자격까지 링크나 프론트 상태에 밀어넣었다.
빠르게 동작은 했지만 운영이 곤란해졌다. 링크가 유출되면 권한 경계가 풀리고, 프론트에 너무 많은 결정 권한을 주면 브라우저마다 다른 동작이 생겼다. 무엇보다 장애가 났을 때 "이 링크가 어느 상태인가"를 서버가 정확히 말할 수 없었다.

깨진 가정: 개방성과 권한은 같은 층이 아니다
문제는 "계정 없음 = 검증 없음"으로 본 데 있었다. 게스트여도 최소한의 identity는 브라우저에서 받아야 했고, 실제 권한은 서버가 발급한 자격으로 결정해야 했다. 둘을 같은 저장소에 두면 둘 다 흔들린다.
WebRTC 연결 실패, TURN credential 실패, 장치 권한 거부, feature grant 누락, expired session은 모두 다른 문제다. 한 줄의 "입장 실패"로 합치면 복구 경로를 설계할 수 없다. 운영자가 링크 하나로 모든 상태를 설명하려다 보면, 사용자는 매번 다른 이유로 문 앞에서 쫓겨난다.
기준을 바꾼다: public link와 private authority 분리
다시 잡은 경계는 명확했다.
| 구성요소 | 역할 | 가지지 말아야 할 것 |
|---|---|---|
| public link | 초대 진입점 | 권한 판단 로직 |
| guest identity | 최소 식별 | 민감 정보 |
| BFF grant | session token + feature grants | provider secret |
| room runtime | grant 기준 UI/API 선택 | 영구 저장 책임 |
| feature limit | 기능 켜기/끄기 | 결제 상태 판단 |
게스트 identity는 브라우저에서 최소한으로 받고, 실제 권한은 BFF가 발급한 session token과 feature grants로 결정한다. room runtime은 이 grant를 기준으로 어떤 패널을 보여줄지, 어떤 API를 호출할지, 어떤 실패 메시지를 보여줄지 정한다.
프론트엔드는 단순한 방 UI가 아니다
이 구조에서 프론트엔드는 "방을 그리는 화면"이 아니라 BFF에서 받은 런타임 설정을 소비하는 클라이언트가 된다. BFF에서 feature grants, mesh room authority, TURN credentials, upload URLs, session tokens를 받아 방 기능을 켠다.
중요한 건 브라우저에 provider secret이나 admin key가 절대 들어가지 않는다는 점이다. 공개 가능한 런타임 설정만 들어가고, 진짜 권한 판단은 서버 경계 안에서만 일어난다. 그래서 링크는 누구나 가질 수 있되, 링크가 여는 문의 크기는 서버가 조절한다. 링크가 열린 문과 서버가 조절하는 문의 크기가 분리되어야, 유출되어도 피해가 통제된다.
운영에서 남긴 규칙
- 링크는 진입점일 뿐, 권한 판단은 서버 경계에서만
- 게스트 identity는 최소 식별만, 민감 정보는 서버 세션에
- 5가지 실패(연결/TURN/권한/grant/만료)는 각각 다른 문장으로
- 프론트에는 provider secret 금지, 런타임 설정만
- 링크 유출 시 revoke는 권한 계층에서만 처리
이때부터 링크 기반 방을 invitation UX가 아니라 state explanation UX로 보게 됐다. 사용자가 링크를 눌렀을 때 무슨 상태인지 설명할 수 있어야, 개방성이 무책임으로 안 읽힌다.
마치며
계정 없는 링크는 만남의 마찰을 줄이는 좋은 도구지만, 그 자체로 보안 모드가 아니다. 링크의 개방성과 권한의 명확함을 같은 층에 두면 둘 다 무너진다.
지금은 이렇게 본다. 링크는 누구나 가질 수 있되, 링크가 여는 문의 크기는 서버가 조절한다. 그 경계를 지키는 게 "계정 없이 만난다"는 약속을 지키는 길이다. 다음에 같은 제품을 만나면 링크를 먼저 그리고, 그 링크가 열어 줄 수 있는 상태의 종류를 두 번째로 그리겠다.