현재 HaruFit이라는 개인 프로젝트를 진행하면서 추후 유저 간의 커뮤니티 기능과 각각의 계정 별 저장된 운동 기록 및 정보들을 관리하기 위해 계정 정보를 관리하기 위해 사용자를 인증하는 과정을 추가해야 한다.
이를 위해 알아야 하는 개념인 JWT의 기본 개념에 대해서 먼저 정리해보도록 하자
머저 JWT는 JSON Web Token의 약자로 서버-클라이언트 간 인증을 위해 널리 사용되는 토큰 기반 인증방식이다!
서버가 로그인 성공 시 사용자 정보를 기반으로 토큰을 생성하면, 클라이언트는 이후 API 요청 마다 이 토큰을 HTTP 헤더에 포함해 보내게 되고, 서버는 이 토큰을 검증해 사용자를 인증하게 된다.
JWT의 3가지 구성 요소
- Header
- 어떤 해싱 알고리즘을 쓰는지
- JWT 타입
{
"alg": "HS256",
"typ": "JWT"
}
- Payload
- 실제 유저 정보, 토큰 만료시간 등
{
"userId": 1234,
"role": "admin",
"exp": 1672531199
}
- Signature
- Header와 Payload를 합친 뒤, 비밀키로 서명한 값
- 클라이언트는 이 서명 덕분에 이 토큰이 서버에서 발행된 것 임을 증명 받게 되고
최종적으로 header.payload.signature 세 부분을 각각 Base64로 인코딩한 문자열을 . 로 구분해 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... 형태로 만든다.
왜 JWT를 사용하는 걸까
- Stateless (무상태) : 서버에 세션(사용자 정보)을 별도로 저장하지 않아도 된다는 점
- 확장성 : 여러대의 서버 환경이나 서버리스에서도 동일한 비밀키만 공유하면 인증이 가능한 점
- Payload 활용 : 토큰 안에 인증된 사용자 정보(Claim)를 담아 클라이언트가 디코딩해 간단한 정보를 확인할 수 있다.
주의 사항
- 토큰의 탈취 위험 : 일단 토큰이 유출되면 만료 전까지 도용이 가능하다는 점
- 반드시 HTTPS로 통신해야 하고, Keychain 등 안전한 저장소에 보관이 필요하다.
- Payload가 누구나 디코딩 가능하다는 점 : 무결성은 보장되나, 기밀성이 보장되지 않는다.
- 서버-클라이언트 시계 차이 : 만료 시점을 체크할 때 클라이언트의 로컬 시간을 그대로 믿으면 안되고 서버가 권한을 최종 검증해야 한다.
이제 여기까지 JWT에 대해서 간략하게 개념에 대해서 다뤄보았는데 이제 이러한 개념을 바탕으로 나는 개인 프로젝트이기 때문에 리소스를 줄이기 위해 Firebase Auth를 통해 JWT를 대체하는 방식을 사용하려고 한다.
먼저 예시를 통해서 Firebase Auth를 사용해보자
Firebase Auth
import SwiftUI
import FirebaseAuth
import FirebaseCore
final class FirebaseAuthViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var user: User?
func signIn() {
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
print("로그인 실패:", error.localizedDescription)
return
}
guard let user = result?.user else { return }
self.user = user
// 여기서 user.getIDToken()을 호출하면, Firebase가 발급한 JWT를 얻을 수도 있음
print("로그인 성공, UID:", user.uid)
}
}
func signUp() {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("회원가입 실패:", error.localizedDescription)
return
}
guard let user = result?.user else { return }
self.user = user
print("회원가입 성공, UID:", user.uid)
}
}
func signOut() {
do {
try Auth.auth().signOut()
self.user = nil
print("로그아웃 성공")
} catch {
print("로그아웃 실패:", error.localizedDescription)
}
}
func fetchTokenManually() {
guard let currentUser = Auth.auth().currentUser else { return }
currentUser.getIDToken { token, error in
if let token = token {
print("Firebase ID Token:", token)
// 이 토큰은 사실상 JWT이며, Base64 디코딩 가능
}
}
}
}
Firebase SDK가 내부적으로 Token Refresh도 진행될 수 있기 때문에 Access Token / Refresh Token을 다룰 필요가 거의 없어진다.
위 코드에서 확인 가능하듯이 getIDToken() 메서드를 통해 원한다면 JWT 문자열을 직접 받아 확인할 수 있다.
Firebase 서버 측 동작
- 인증이 성공하면 Firebase는 이 사용자(uid: user.uid) 가 유효함을 증명하는 ID Token을 발급한다.
- iOS 앱 내 firestore/Realtime DB에 접근할 때, Firebase SDK가 ID Token을 전송 → Firebase 서버가 이 토큰을 검증해 인증된 사용자 임을 확인
- 만약 토큰이 만료됐거나 위조됐다면, Firebase 서버는 자동으로 에러를 반환한다.
- 클라이언트에서는 FirebaseAuth 라이브러리가 자동으로 재인증을 시도해 재로그인 과정을 유도할 수 있다.
Security Rules
Firestore나 Realtime DB에서는 보안 규칙을 정의해 인증된 사용자만 읽기/쓰기가 가능하도록 설정할 수 있다.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /haruFit/{document} {
// 로그인(인증)된 사용자만 읽기/쓰기가 가능
allow read, write: if request.auth != null;
}
}
}
결론적으로 Firebase Auth는 JWT 인증을 더 쉽게 구현해주는 클라우드 서비스라고 볼 수 있다. 따라서 JWT를 직접 구현하는 로직 대신, Firebase Auth를 사용하면 토큰 발급, 만료, 갱신 절차를 자동화할 수 있다는 점이 큰 장점이라고 할 수 있다 🙂
오늘은 JWT에 대한 개념을 한번 다뤄보았다!
'◽️ Programming > iOS' 카테고리의 다른 글
Swift Format , Swift Lint 프로젝트에 적용하기 (0) | 2025.01.07 |
---|---|
Deadlock에 대해서 알아보자!! (0) | 2024.12.13 |
멀티 쓰레드 환경에서 Race Condition에 대해서 알아보자 (0) | 2024.12.09 |
WKWebView를 사용해 웹페이지 가져오기 (1) | 2024.12.02 |
@AppStorage , UserDefaults의 특징 (0) | 2024.10.18 |