본문으로 건너뛰기

Vapor 백엔드에 사용자와 bearer-token 인증 추가하기 (3)

Add Users and Authentication to My First Vapor Backend in 2026 Pt. 3

swiftsips.dev

핵심 요약

  1. User와 UserToken 모델을 추가하고 비밀번호 해시와 Postgres 기반 bearer token으로 가입·로그인 및 인증 사용자 조회를 구현한다.
  2. Link에 user_id 관계를 연결하고 모든 링크 조회·수정·삭제에서 현재 사용자 ID를 조건으로 적용해 사용자 간 데이터 접근을 차단한다.
  3. 인증이 필요한 링크 API와 /api/me 경로를 구성하고, 모델에 저장된 passwordHash를 노출하지 않도록 공개 응답 DTO를 별도로 사용한다.

도입

이전 단계의 LinkVault는 Postgres를 사용하는 CRUD API였지만, 링크가 사용자와 연결되지 않아 모든 데이터의 소유자를 구분할 수 없었다. 이 단계에서는 User와 UserToken 모델을 만들고 비밀번호 해시, 데이터베이스에 저장되는 bearer token, 인증 그룹을 도입한다. 링크 모델에도 사용자 관계를 추가해 각 사용자가 자신의 링크만 관리하도록 바꾼다.

상세 요약

인증 데이터 모델

User 모델은 이메일, 이름, password_hash, 생성 시각을 저장한다. 가입 시 이메일 앞뒤 공백을 제거하고 소문자로 바꾸며, 데이터베이스의 unique 제약으로 중복 이메일을 막는다. UserToken은 토큰 값과 user_id를 저장한다. 토큰과 사용자는 다대일 관계이며, 사용자를 삭제하면 연결된 토큰도 함께 삭제되도록 설정한다.

Link에도 user_id를 필수 필드로 추가한다. 따라서 링크를 생성할 때 소유 사용자 ID가 필요하다. 마이그레이션은 users를 먼저 등록하고 그 뒤에 links, user_tokens를 등록한다. 두 테이블이 users를 참조하기 때문이다. 기존 마이그레이션 파일을 고치는 방식은 로컬 데이터베이스를 다시 만들 수 있는 초기 튜토리얼 상황에 한정해 설명하며, 이미 배포한 환경에서는 새 마이그레이션을 추가하는 방식을 언급한다.

가입·로그인과 토큰 인증

가입 경로 POST /api/auth/signup은 이메일 중복 여부를 확인한 뒤 Vapor의 비동기 password API로 비밀번호를 해시해 저장한다. 평문 비밀번호는 데이터베이스에 저장하지 않는다. 로그인 경로 POST /api/auth/login은 제출한 비밀번호와 저장된 해시를 검증한다. 이메일이 없거나 비밀번호가 맞지 않으면 같은 unauthorized 응답을 반환한다.

토큰은 32개의 암호학적으로 안전한 난수 바이트를 생성해 Base64 문자열로 인코딩한다. 이 구현은 macOS와 Linux에서 난수를 생성한다. 토큰은 Postgres에 저장되며, 클라이언트는 이후 요청의 Authorization: Bearer <token> 헤더에 담아 보낸다. 튜토리얼은 JWT 대신 데이터베이스 기반 토큰을 사용하며, 저장된 토큰은 나중에 삭제해 폐기할 수 있다.

UserTokenAuthenticator는 헤더의 토큰 값과 일치하는 레코드를 찾고 연결된 사용자를 불러와 request.auth에 로그인 상태로 등록한다. 인증된 경로 안에서는 req.auth.require(User.self)로 사용자를 가져온다. GET /api/me는 이 사용자 정보를 반환한다. 인증 응답에는 전체 User 모델 대신 UserResponse를 사용해 passwordHash가 공개되지 않도록 한다.

링크 경로의 사용자 소유권

기존 링크 경로는 인증 그룹 아래로 이동한다. 생성 요청은 인증된 사용자의 ID를 새 링크에 저장하고, 목록 조회는 해당 ID의 링크만 가져온다. 단건 조회·수정·삭제도 링크 ID만으로 검색하지 않는다. 링크 ID와 현재 사용자 ID를 함께 조건으로 적용하고, 일치하는 레코드가 없으면 not found를 반환한다. 따라서 다른 사용자의 링크를 ID로 지정해도 조회하거나 변경할 수 없다.

추가되는 경로는 가입, 로그인, 사용자 조회이며, 기존 POST, GET, PATCH, DELETE /api/links 경로는 인증이 필요하다. 로컬 데이터베이스를 초기화할 때 docker compose down -v를 사용하면 프로젝트의 Postgres 볼륨이 삭제되므로, 중요한 데이터가 있는 환경에서 실행하지 않도록 경고한다. 다음 단계에서 다룰 요청 검증과 컨트롤러 구조는 Vapor 백엔드에 검증과 컨트롤러 구조 적용하기 (4)에서 이어진다.

결론

핵심은 인증 토큰으로 사용자를 식별하는 데 그치지 않고, 링크를 읽거나 변경할 때마다 현재 사용자의 ID를 쿼리에 포함하는 것이다. 가입·로그인은 해시 검증과 토큰 발급을 수행하고, /api/me와 링크 경로는 인증된 사용자만 처리한다. 이로써 LinkVault는 사용자 소유권을 적용한 API가 된다. 다음 단계에서는 입력 검증과 컨트롤러 분리를 다룬다.