반응형

환경 설정

* 환경 : firebase DB & REST API

 

1. firebase에서 프로젝트를 선택(or 생성)합니다.

2. Database -> 규칙

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

3. Authntication -> 로그인 방법 -> 이메일/비밀번호 활성화


유저 생성

 

아래와 같이 구글에서 firebase auth rest api를 검색합니다.

https://firebase.google.com/docs/reference/rest/auth

 

Firebase Auth REST API  |  Firebase

API Usage You can query the Firebase Auth backend through a REST API. This can be used for various operations such as creating new users, signing in existing ones and editing or deleting these users. Throughout this document, API_KEY refers to the Web API

firebase.google.com


사이트 오른쪽 목차에서 Sign up with email / password 로 아래와 같은 조건을 만족시키는 통신을 합니다.

 

Method: POST

Content-Type: application/json

Request Body Payload

Property NameType Description
email string 유저 생성을 위한 email
password string 유저 생성을 위한 비밀번호
returnSecureToken boolean

ID 와 새로고침 토큰을 반환 하는지 안하는지, 항상 true입니다.

 

post로 https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY] 를 합니다.

API_KEY는 화면 왼쪽 drawer 에서 톱니바퀴(설정)을 눌러 확인할 수 있습니다.

내 프로젝트 [웹 API 키]를 복사하여 이용합니다.

// auth.service.ts
signup(email: string, password: string) {
  return this.http
    .post(
    'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC4NdTkUmluvTX-wfuFKVZPdFCXvZKiWEQ',
    {
      email: email,
      password: password,
      returnSecureToken: true
    }
)

Observable을 반환하기 때문에 return을 사용해줍니다.

Response Payload

Property NameType Description
idToken string 신규 유저의 Auth ID 토큰.
email string 신규 유저를 위한 email.
refreshToken string 신규 유저를 위한 Auth 새로고침 토큰.
expiresIn string ID 토큰의 만료를 초를 숫자로 나타냅니다.
localId string The uid of the newly created user.

리스폰스에 알맞는 데이터를 받기 위해 아래와 같은 인터페이스를 만들어 포스트의 리턴타입을 입력합니다.

// auth.service.ts
export interface AuthResponseData {
  idToken: string;
  email: string;
  refreshToken: string;
  expiresIn: string;
  localId: string;
}

signup(email: string, password: string) {
  return this.http
  .post<AuthResponseData>(
  'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC4NdTkUmluvTX-wfuFKVZPdFCXvZKiWEQ',
  {
    email: email,
    password: password,
    returnSecureToken: true
  }
)
// auth.component.ts

onSubmit(form: NgForm) {
  if (!form.valid) {
    return;
  }
  const email = form.value.email;
  const password = form.value.password;
  
  this.authService.subscribe(
    resData => {
      console.log(resData);
    },
    errorMessage => {
      console.log(errorMessage);
    }
  );
  form.reset();
}

로딩 스피너

1. 컴포넌트 만들기

share폴더에

  loading-spinner 폴더와 ts, css파일을 생성합니다.

    loading-spinner.component.ts

    loading-spinner.component.css

 

구글에 검색합니다. css loading spinners https://loading.io/css/

해당 사이트에서 원하는 스피너를 선택한 뒤에, css에 값과 html값을 복붙합니다.

// loading-spinner.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-loading-spinner',
  template:
    '<div class="lds-ring"><div></div><div></div><div></div><div></div></div>',
  styleUrls: ['./loading-spinner.component.css']
})
export class LoadingSpinnerComponent {}

2. HTML에 추가하기

<!-- auth.component.html -->

<div class="row">
  <div class="col-xs-12 col-md-6 col-md-offset-3">
    <div *ngIf="isLoading">
      <app-loading-spinner></app-loading-spinner>
    </div>
    <form #authForm="ngForm" (ngSubmit)="onSubmit(authForm)" *ngIf="!isLoading">
      <!-- ... -->
    </form>
  </div>
</div>

form 태그에 isLoading을 이용하여 조건별로 나타날 수 있도록 합니다.

에러 핸들링

UI를 위해 error를 추가합니다.

<!-- auth.component.html -->

<div class="row">
  <div class="col-xs-12 col-md-6 col-md-offset-3">
  <div class="alert alert-danger" *ngIf="error">
      <p>{{ error }}</p>
    </div>
    <div *ngIf="isLoading">
      <app-loading-spinner></app-loading-spinner>
    </div>
    <form #authForm="ngForm" (ngSubmit)="onSubmit(authForm)" *ngIf="!isLoading">
      <!-- ... -->
    </form>
  </div>
</div>
// auth.component.ts

isLoading = false;
error: string = null;

onSubmit(form: NgForm) {
  if (!form.valid) {
    return;
  }
  const email = form.value.email;
  const password = form.value.password;
  this.isLoading = true;
  
  this.authService.subscribe(
    resData => {
      console.log(resData);
      this.isLoading = false;
    },
    errorMessage => {
      console.log(errorMessage);
      this.error = errorMessage;
      this.isLoading = false;
    }
  );
  form.reset();
}

 

firebase auth rest api 에서 조금 더 아래로 내리면 아래와 같은 내용을 발견할 수 있습니다.

 

Common error codes

  • EMAIL_EXISTS: The email address is already in use by another account.
  • OPERATION_NOT_ALLOWED: Password sign-in is disabled for this project.
  • TOO_MANY_ATTEMPTS_TRY_LATER: We have blocked all requests from this device due to unusual activity. Try again later.

errorRes를 보면, 

error안에 error가 있고, message를 보면, EAMIL_EXISTS와 같은 코드를 받을 수 있습니다.

 

에러 코드를 이용하면 사용자에게 쉽게 안내할 수 있습니다.

errorRes => {
  console.log(errorRes);
  switch (errorRes.error.error.message) {
  case 'EMAIL_EXISTS':
  this.error = 'This email exists already';
  break;
  case 'EMAIL_NOT_FOUND':
  this.error = 'This email does not exist.';
  break;
  case 'INVALID_PASSWORD':
  this.error = 'This password is not correct.';
  break;
  }
}

로그인 만료 시간 핸들링

로그인 하기 전에는 브라우져로 다시 들어갈 경우, 지속적으로 로그인 상태가 됩니다.

이런 상황을 방지하기 위한 방법 입니다.

 

Response Payload

Property NameTypeDescription

expiresIn string The number of seconds in which the ID token expires.

expiresIn은 ID토큰의 만료 시간입니다.

const expirationDate = new Date(new Date().getTime() + expiresIn * 1000);

곱하기 1000을 해서 밀리세컨드(ms = 1/1000 초)를 하여 생성되는 아이디의 초과 시간을 Local에 저장해 둡니다.


Auto Loggin

 

로컬 스토리지에 userData저장.

localStorage.setItem('userData', JSON.stringify(user)); // JSON.stringify()를 꼭사용해주세요.

 

불러오기

autoLogin() {

	// userData snapshot을 불러옵니다.
    const userData: {
      email: string;
      id: string;
      _token: string;
      _tokenExpirationDate: string;
    } = JSON.parse(localStorage.getItem('userData')); // JSON.parse로 반드시 파싱합니다.
    if (!userData) { // 데이터가 없을경우 끗.
      return;
    }

    const loadedUser = new User(
      userData.email,
      userData.id,
      userData._token,
      new Date(userData._tokenExpirationDate)
    );

	// 로그인 제한시간을 확인합니다.
    if (loadedUser.token) {
      this.user.next(loadedUser);
      const expirationDuration =
        new Date(userData._tokenExpirationDate).getTime() -
        new Date().getTime();
      this.autoLogout(expirationDuration);
    }
  }

Guard (Router Protection)

// auth.guard.ts
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot
} from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    router: RouterStateSnapshot
  ):
    | boolean
    | Promise<boolean>
    | Observable<boolean> {
    return this.authService.user.pipe(
      map(user => {
        return !!user;
      })
    );
  }
}

Activate 인터페이슨는 route와 state(RouterStateSnapshot)을 필요로 합니다.

또한 boolean, Promise<boolean>, Observable<boolean>의 형태로 반환해야 합니다.

Authenticated User는 pipe(map())으로 묶어서 반환합니다.

 

// app-routing.module.ts

canActivate: [AuthGuard]//를 추가합니다

리다이렉트 시키기!

URLTree를 이용하여 원하는 곳으로 이동시킬 수 있습니다.

constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    router: RouterStateSnapshot
  ):
    | boolean
    | UrlTree
    | Promise<boolean | UrlTree>
    | Observable<boolean | UrlTree> {
    return this.authService.user.pipe(
      take(1), // 불필요한 subscribe를 방지합니다. 1번만 실행.
      map(user => {
        const isAuth = !!user;
        if (isAuth) {
          return true;
        }
        return this.router.createUrlTree(['/auth']); // URLTree를 생성하여 login하도록 리다이렉트.
      })
      // angular 옛 버전에서는 직접 리다이렉트 주소를 기입해야 합니다.
      // tap(isAuth => {
      //   if (!isAuth) {
      //     this.router.navigate(['/auth']); // 일일히 리다이렉트 주소 입력.
      //   }
      // })
    );

 

 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Service VS EventEmitter  (0) 2020.02.19
Dynamic Component  (0) 2019.09.13
Http  (0) 2019.08.20
Pipe  (0) 2019.08.15
Form - 복습 및 보충 내용  (0) 2019.08.14

+ Recent posts