서론
Angular 와 Firebase 는 궁합이 잘 맞는다.
몇 년 전만 해도 Angular 공식 문서에서는 deploy 시 Firebase 를 활용하라고 추천하기까지 했다. 지금은 github 등 다양한 옵션을 제공한다.
자세히 보고싶으면 아래 링크로... ↓↓↓
Angular
angular.io
Angular는 firebase 활용을 돕기 위한 공식 라이브러리까지 지원한다.
둘 다 구글에서 관리하기 때문에 일종의 락인 효과를 노린게 아닐까? 싶은 생각도 든다.
각설하고, 오늘의 문제는 무엇이었는가, 바로 Firebase 에서 제공하는 auth를 활용해 로그인 기능을 구현할 시에 Angular에서 기본으로 제공하는 Guard를 함께 활용하는 것이었다.
Angularfire 문서에서는 아래처럼 ngIf를 활용하는 예시를 보여준다.
<div *ngIf="auth.user | async as user; else showLogin">
<h1>Hello {{ user.displayName }}!</h1>
<button (click)="logout()">Logout</button>
</div>
<ng-template #showLogin>
<p>Please login.</p>
<button (click)="login()">Login with Google</button>
</ng-template>
그러나 여러 페이지 간에 라우팅을 하고, 사용자 로그인 여부에 따라 페이지 노출 여부가 달라져야 한다면 각 템플릿 별로 저런식의 ngIf를 구현한다는 것은 너무나도 많은 관리 측면의 문제점들을 초래할 것이다.
Angular는 기본적으로 routing guard 기능을 지원한다. 라우팅 모듈에 CanActivate를 설정해 특정한 조건을 만족하는 경우에만 해당 페이지로 라우팅할 수 있게끔 하는 것이다.
그래서...
오늘의 목표: firebase 로 로그인을 구현하고, routing guard 를 활용해 로그인 여부에 따라 페이지 접근 권한을 제어해보자!
firebase auth 활용시 guard 사용이 애매한 이유
AngularFire는 친절하게도 router-guards 를 제공한다.
그런데 안타깝게도 이것저것 기능들을 덧붙이고 싶은데 내 능지가 딸리는지 조금 불편하다는 기분이 들었다.
그런데 코드를 뜯어보니, 기본적으로 @angular/fire에서 Auth를 import 해와서 Auth 가 null인지 유무로 현재 로그인된 사용자가 있는지를 판단하는 로직이었다.
이런 경우에는 문제가 발생할 수 있는데, firebase의 auth 정보는 때에 따라 로그인되어 있더라도 null 값이 나올 수 있기 때문이다.
라우팅 시 firebase 가 제대로 initialize 되지 않은 경우에, auth 정보가 없는 채로 페이지 이동이 이루어질 수 있으므로, AngularFire가 제공하는 기능이 늘 올바르게 동작할 것이라고 기대하긴 어렵다는게 내 최종적인 판단이었다.
firebase 문서에서도 이러한 점을 지적하고 있다.
Note: currentUser might also be null because the auth object has not finished initializing. If you use an observer to keep track of the user's sign-in status, you don't need to handle this case.
구현하는 방법
그렇다면 어떻게 하냐?
여기에선 크게 두 가지 방법이 갈린다. LocalStorage를 활용해 현재 로그인 유무를 로컬에 저장해놓는 방법이 첫 번째이고, 두 번째는 Guard 의 canactivate 내에서 firebase 가 제대로 init 될 때 까지 기다렸다가 사용자 정보를 받아오는 것이다.
LocalStorage 활용
첫번째 방법은 단순하다.
어디에서든 auth 의 상태를 추적하고 있다가 변경이 발생할 때마다 아래처럼 localStorage 의 값을 변경해주면 된다.
import { Auth, onAuthStateChanged } from '@angular/fire/auth';
constructor(@Optional() private auth: Auth) {
onAuthStateChanged(this.auth, (user) => {
if (user) {
// User is logged in
localStorage.setItem('isLoggedIn', 'true');
} else {
// User is signed out
localStorage.setItem('isLoggedIn', 'false');
}
});
}
그리고 guard 의 canActivate 부분은 대충 아래처럼 해주면 된다.
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean{
const isLoggedIn = localStorage.getItem('bgcolor') === 'true';
if(isLoggedIn){
return true
}else{
return false
}
}
그런데 뭔가 나는 콘솔창에다가 한 줄만 입력하면 내용을 얼마든지 바꿀 수 있는 localStorage에 나름대로 보안 관련된 사항인데 이걸 저장하는게 맞는건가? 싶은 생각이 들었다. 그러나 이런저런~ 조사를 진행해 본 결과, 크게 개의치 않아도 될 것 같다는 결론에 도달했다. 무엇보다 firebase 얘네들도 JWT를 발급하면 localStorage 에 저장한다.
이런 방식은 일단 빠르다는 장점도 있다. 매번 firebase가 initialize 하길 기다릴 필요 없이 localStorage 에 접근해 로그인 여부를 가져오면 그만이다.
그러나 나는 뭔가 더 세련됐으면 좋겠다는 생각을 했다. 이유는 모르겠는데 localStorage를 별 고민없이 사용하면 뭔가 실력 없는 개발자라는 생각이 든다.?!
거기다 모종의 이유로 갑작스럽게 localStorage 정보가 바뀌지 않은 채로 사이트가 튕겨버린다면..? 그 상태로 JWT가 만료된다면!? 하는 끔찍한 생각을 하게 됐고, 결국 두 번째 방법에 종착했다.
Initialize 될 때 까지 기다려서 auth 를 받아오는 방법
나는 Promise 를 활용하기로 했다.
이 방법엔 일단 auth 를 처리하는 service가 하나 필요하다.
거기 안에 다음처럼 함수를 만든다.
import { Auth, onAuthStateChanged } from '@angular/fire/auth';
constructor(@Optional() private auth: Auth) { }
checkLoggedIn():Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const unsubscribe = onAuthStateChanged(this.auth, (user) => {
if (user) {
resolve(true);
unsubscribe();
} else {
resolve(false);
unsubscribe();
}
})
})
}
그리고 guard의 CanActivate는 다음과 같이 만든다.
async canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Promise<boolean>{
const isLoggedIn = await this.authService.checkLoggedIn();
if(isLoggedIn){
return true
}else{
return false
}
}
checkLoggedIn()을 service 내에 만드는 것은 guard 에서 바로 불러오면 모듈을 모듈 밖에서 불러왔다고 에러가 뜨기 때문이다.
이 방법은 좀 느리다는 단점이 있다.
대신에 좀 더 안정적지 않나 하는 생각이 들었다. 페이지가 변경될 때마다 현재의 auth 상태를 받아오게 되므로, 혹시라도 로그인 여부를 잘못 판단하는 일이 없을 것이다.
혹시 더 좋은 방법이 있거나, 제가 잘못 생각한 지점을 발견하신 분이 있다면 알려주시면 사례는 못해드리지만 큰 감사를 드립니다.
'Web & PWA' 카테고리의 다른 글
안드로이드 공유 리스트에 내 앱 뜨게 하기! (0) | 2021.08.24 |
---|---|
버튼 클릭 시 키보드 고정 (Ionic Angular) (0) | 2021.08.18 |
TEXTAREA Autosizing Scroll Jumping 버그 해결 (0) | 2021.08.17 |
ios Safe Area : 앱 컨텐츠가 아이폰 노치와 겹친다면!!!!! (0) | 2021.04.19 |