SniffMEET. 세션 갱신과 정보 접근을 SessionManager에 캡슐화하기

SessionManager 사용성 개선 세션을 갱신하는 방법 Supabase 서버와의 통신이 필요할 때, 우선 세션의 유효성을 검증해야 한다. 기존 방식에선 직접 AuthManager의 싱글톤 객체에서 세션을 갱신하는 작업을 했지만 AuthManager와 SessionManager의 역할 명확하게 분리 AuthManager의 싱글톤 해제 와 같은 변화점이 있었고, 기존 AuthManager가 담당하던 세션 갱신, 복원 작업이 SessionManager로 옮겨졌다. SessionManager의 싱글톤 객체에서 세션의 유효성을 체크하는 컴퓨티드 프로퍼티의 부울리언 값을 체크하여 세션을 갱신하는 메소드를 호출하는 방식을 사용했다: if try SessionManager.shared.isExpired { try await SessionManager.shared.refreshSession() } 이 방식은 SessionManager의 싱글톤 객체 호출이 두 번 일어나고, refreshSession이라는 메소드를 직접 노출해서, SessionManager가 세션을 갱신하는 방법을 직접 노출하는 문제가 있다. ...

February 10, 2025

SniffMEET. ISP로 Supabase 요청 빌더 역할 나누기

들어가며 Supabase REST API를 직접 감싸서 사용하다 보니, 유즈케이스 레이어에서 eq., in., gte. 같은 쿼리 문자열을 직접 조립하는 코드가 늘어났다. 처음에는 단순했지만, 요청이 많아질수록 오타 가능성이 커지고 Supabase의 세부 문법이 상위 레이어로 새어 나오는 문제가 있었다. 이번 글은 이 쿼리 구성 책임을 Supabase DB Layer 안으로 옮기기 위해 요청 빌더와 쿼리 파라미터 래퍼를 도입한 과정을 정리한다. 기본 구조 SNMNetwork의 동작을 간단하게 설명하면, SNMRequestConvertible을 채택하는 객체의 정보로 네트워크 요청을 생성하고, 통신한 다음, 응답을 반환하는 방식으로 동작한다. ...

February 6, 2025

SniffMEET. anon-key와 익명 사용자의 접근 권한 구분

anon-role과 anon-user의 접근 권한 차이 Public Scheme에 있는 테이블을 anon-key로는 접근할 수 있었지만, anon-user의 JWT 토큰으로는 접근하지 못하는 문제가 있었다. 처음에는 둘이 비슷한 범주라고 생각했지만, 실제로는 권한 체계와 접근 방식이 달라서 같은 문제로 볼 수 없었다. 문제를 확인하기 전 생각한 내용 anon-key, anon-role, anon-user의 차이부터 다시 정리했다. anon-key를 이용한 접근은 anon-role로 보고, anon-user는 이메일 주소나 전화번호를 등록하지 않은 유저를 의미한다. 이 경우 role 자체는 authenticated-role이었다. 익명 유저가 접근했을 때 200 OK가 뜨지만 실제 값을 가져오지 못하는 경우도 있었다. 그래서 접근 자체는 허용되지만 RLS에서 막히고 있다고 추정할 수 있었다. ...

November 28, 2024

SniffMEET. Storage 버킷과 접근 제어

SniffMEET의 프로필 이미지는 사용자 정보와 성격이 다르다. 사용자 이름이나 소개처럼 구조화된 데이터는 Database에 저장하고, 이미지 파일은 Storage에 저장하는 편이 적합하다. Storage의 Postgres 테이블에는 파일 자체가 아니라 버킷과 객체의 메타데이터가 기록된다. 버킷과 객체 경로 버킷은 파일을 묶는 최상위 컨테이너다. 사용자마다 버킷을 만드는 것보다 용도별 버킷을 만들고 객체 경로로 사용자를 구분하는 편이 관리하기 쉽다. profile-images/ ├── {user-id}/profile.jpg └── {user-id}/thumbnail.jpg 이 구조에서는 profile-images 버킷이 공통된 공개 범위와 파일 제한을 담당하고, 사용자 ID가 포함된 경로를 RLS 정책에서 검사할 수 있다. ...

November 24, 2024

SniffMEET. Supabase 세션 갱신 요청이 실패한 이유

문제 Supabase 세션 갱신 요청이 제대로 동작하지 않았다. 세션 로직이나 네트워크 레이어 문제처럼 보였지만, 실제 원인은 요청 형식이었다. 당시 메모는 이랬다. 수정 "grant_type": "refreshToken" grant_type 값을 Swift식 camelCase로 보내고 있었다. Supabase가 기대하는 값은 refresh_token이었다. "grant_type": "refresh_token" body도 마찬가지였다. Supabase가 기대하는 key는 refreshToken이 아니라 refresh_token이었다. refresh 요청에서는 기존 access token의 Authorization 헤더도 제거했다. (토큰을 갱신받기 위한 요청인데, 만료된 토큰을 보내는거니까 아무 의미 없다!) body: Data("{ \"refresh_token\": \"\(refreshToken)\" }".utf8) header["Authorization"] = nil refresh 요청은 기존 access token으로 리소스에 접근하는 요청이 아니라, refresh token으로 새 세션을 발급받는 요청이기 때문이다. ...

November 21, 2024

SniffMEET. RLS와 테이블 권한

문제 Supabase에서 anon key로 Public 테이블에 접근하면 데이터가 보이는데, 익명 로그인 후 받은 JWT access token으로 접근하면 200 OK만 오고 데이터가 비어 있었다. 처음에는 authenticated role이 anon role보다 상위 권한이라고 생각해서 더 헷갈렸다. 하지만 Supabase에서는 role의 상하관계보다 RLS 정책을 어떻게 작성했는지가 더 중요했다. 확인 JWT가 만료된 경우에는 401 Unauthorized가 발생했다. 반면 RLS 조건에 맞지 않는 경우에는 요청 자체는 성공해서 200 OK가 오지만, row가 필터링되어 데이터가 내려오지 않았다. authenticated 유저가 읽을 수 있도록 다음 policy를 추가하자 데이터를 읽을 수 있었다. ...

November 20, 2024

SniffMEET. JWT와 Supabase RLS 정책에 연결하기

Supabase Auth로 로그인하면 클라이언트는 Access Token으로 JWT를 받는다. Supabase는 요청에 포함된 JWT를 검증하고, 그 안의 사용자 정보와 역할을 RLS 정책에 전달한다. JWT의 클레임 JWT는 헤더, 페이로드, 서명으로 구성된다. 각 부분은 Base64URL로 인코딩되지만 암호화되지는 않으므로 페이로드에 비밀 정보를 넣으면 안 된다. 서명은 헤더와 페이로드가 변조되지 않았는지 검증하는 데 사용한다. Supabase의 JWT에는 다음과 같은 클레임이 포함된다. sub: 사용자의 고유 ID role: RLS를 적용할 PostgreSQL 역할 iat: 토큰이 발급된 시각 exp: 토큰이 만료되는 시각 Supabase에서 JWT 사용하기 RLS는 PostgreSQL이 행 단위로 접근을 제한하는 기능이다. Supabase에서는 auth.uid()로 현재 JWT의 sub 값을 가져와 행의 사용자 ID와 비교할 수 있다. ...

November 20, 2024

SniffMEET. 익명 로그인 세션의 생성·복원·갱신 흐름

익명 로그인 생성, 복원, 갱신 로직 구현 JWT, Refresh Token, 세션의 개념이 아직 명확하게 정리되지 않은 상태에서 익명 계정을 먼저 만들게 됐다. 개념이 명확하지 않아서, 세션 관리에 꽤 애를 먹었는데, 앱 재실행 이후에도 익명 계정을 어떻게 이어서 사용할지부터 정리할 필요가 있었다. 생성, 복원, 갱신 로직은 서로 밀접하게 연결되어 있어서, 처음 앱을 실행했을 때와 다시 실행했을 때의 흐름을 먼저 구분해야 했다. 실제 문제 해결 과정 먼저 시점별로 세션 상태를 표로 정리했다. ...

November 14, 2024