목차

     

     

    Turorial

    Firebase의 인증, DB, 저장소, 호스팅, 함수(functions) 기능을 사용하여 게임을 운영하는데 필요한 운영툴을 만든다.

     

    운영툴은 Firebase Cloud Firestore 에 있는 클라이언트가 저장한 유저 정보를 가져와 화면에 보여주고 Firebase Storage 에 업로드되어 있는 이미지를 활용하고 특정 관리자 계정만 로그인이 가능하도록 하는 인증 기능을 사용한다.

     

    node.js 로 제작된 웹 운영툴의 완성된 모습은 아래와 같다.

     

    로그인 화면

    Firebase Auth 를 사용하여 인증을 처리한다. 로그인 시 session cookie 가 만들어져 저장된다.

     

    메인 화면

    관리자 계정으로 로그인을 성공하면 아래와 같은 화면으로 들어오게 된다. 

     

    유저 리스트

    Firebase Cloud Firestore에서 클라이언트가 저장한 유저 데이터의 목록을 가지고 온다.

    안드로이드, iOS 이미지의 경우 Firebase Storage 에서 가지고 온 것이다.

     

    유저 디테일

    유저에 대한 디테일한 정보를 보여준다. 

     

     

    Firebase Setting

    운영툴은 많은 Firebase 기능을 사용한다.

     

    1. 먼저 아래 링크에서 새로운 Firebase 프로젝트를 만들거나 기존의 Firebase 프로젝트를 사용한다.

    https://seonbicode.tistory.com/28

     

    2. 새로운 앱을 등록하거나 기존의 앱을 사용한다.

    Web App 만 등록해준다. node.js 서버만 사용할 예정이기 때문이다.

    https://seonbicode.tistory.com/43#Web_App

     

    3. Firebase 기능 중 하나인 Database 를 세팅한다.

    https://seonbicode.tistory.com/43#Database

     

    4. Firebase 기능 중 하나인 Storage 를 세팅한다.

    https://seonbicode.tistory.com/43#Storage

    세팅 후 Firebase Storage 에 3개의 이미지를 업로드 해야한다.

     

    먼저 manageTool 이라는 폴더를 만들어준다..

     

    manageTool 폴더를 눌러 안 쪽으로 진입 후 3개의 이미지 파일을 업로드해야한다. 아래의 압축파일을 다운로드 받는다.

    osicon.zip
    0.01MB

    osicon.zip 파일을 압축해제한 후 안에서 나온 3개의 icon image 들을 manageTool 폴더 안에 파일 업로드 버튼을 눌러 업로드 해준다.

     

    5. Firebase 기능 중 하나인 Hosting 세팅을 한다.

    먼저 node 와 npm 와 express 3가지를 설치한다.

    https://seonbicode.tistory.com/38#Node.js_%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD_%EA%B5%AC%EC%B6%95

    그 후, Firebase Hosting 세팅을 해준다.

    Firebase login, init 같은 경우 차후 서버를 제작할 때 같이 세팅해준다. 일단 세팅이 목적이니 넘어가고 차후에 다시 세팅한다.

    https://seonbicode.tistory.com/43#Hosting

     

    6. Authentication(인증) 세팅을 한다.

    https://seonbicode.tistory.com/43#Authentication

    관리자 이메일 하나를 만들어줘야한다.

    Firebase Console 의 Authentication 화면에서 사용자 추가 버튼을 눌러 관리자 이메일를 하나 만든다.

    이메일 : test@gmail.com 로 입력하고 비밀번호는 임의로 입력한 후 아래에 있는 사용자 추가 버튼을 눌러 사용자를 추가하면 관리자 계정 test@gmail.com이 만들어진다.

     

    7. Firebase 인증에 필요한 Firebase SDK 와 DB, Storage, Hosting 에 필요한 Firebase Admin SDK 를 저장해둔다.

    https://seonbicode.tistory.com/43#Firebase_SDK_snippet

    https://seonbicode.tistory.com/43#_Firebase_Admin_SDK

     

    Firebase SDK 와 Firebase Admin SDK 은 차후 서버 세팅 도중 사용한다.

     

     

    Server

    먼저, 서버의 기본이 되는 Firebase Hosting Project를 기존의 것을 사용하거나 새로 만들어줘야한다.

    새로 만들어야한다면 아래 링크를 참조하여 새로 하나 만들어주자.

    https://seonbicode.tistory.com/38#Firebase_Hosting_%EA%B8%B0%EB%B3%B8_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8_%EA%B0%9C%EB%B0%9C_%ED%99%98%EA%B2%BD_%EA%B5%AC%EC%B6%95

     

    추가로 jsdom 노드 모듈을 추가해줘야한다.

    터미널에서 프로젝트/functions 폴더로 이동 후 npm install jsdom --save 명령어로 jsdom 을 추가한다.


    이 글은 Firebase Hosting Project 를 기준으로 설명하니 준비가 되었다면 시작한다.

     

    운영툴 서버는 1개의 라우터(manage.js) 와 4개의 ejs(login.ejs, top.ejs, userlist.ejs, userlistdetail.ejs) 로 이루어져있다.

     

    manage.js

    먼저 아래에 첨부된 manage.js 파일을 프로젝트/functions 폴더에 넣어준다.

    manage.js
    0.01MB
    // express 
    var express = require('express');
    // 쿠키 사용을 도와주는 노드 모듈
    var cookieParser = require('cookie-parser');
    
    // 로컬 테스트용
    //var jsdom = require('../functions/node_modules/jsdom');
    // 배포할때는 아래껄 사용해야한다.
    var jsdom = require('jsdom');
    const { JSDOM } = jsdom;
    const { window } = new JSDOM();
    const { document } = (new JSDOM('')).window;
    global.document = document;
    
    var router = express.Router();
    
    // firebase Admin 선언
    const admin = require('firebase-admin');
    
    // firebase Admin 초기화
    const firebaseAdmin = admin.initializeApp({
      credential: admin.credential.cert({
        firebase Admin SDK 키값 넣는 자리
      }),
    });
    
    // storageBucket 주소값을 넣어준다.
    const firebaseConfig = {
      storageBucket: "",
    };
    
    let db = firebaseAdmin.firestore();
    
    // 쿠키파서 초기화
    router.use(cookieParser());
    
    // 쿠키가 있는지 체크
    async function checkCookie(req, res) {
      const work = async () => {
        try {
          // __session 이라 선언한 쿠키를 가지고 온다.
          const sessionCookie = req.cookies.__session || '';
    
          // Verify the session cookie. In this case an additional check is added to detect
          // if the user's Firebase session was revoked, user deleted/disabled, etc.
          await firebaseAdmin.auth().verifySessionCookie(sessionCookie.toString(), true /** checkRevoked */);
    
          return true;
        } catch (err) {
          console.log(err);
    
          return false;
        }
      }
    
      return await work();
    }
    
    // 이미지 이름으로 Firebase Storage 에서 이미지 다운로드 링크 받아오기
    // 서명 되어 있고 기한이 있는 링크
    async function getImageLongLink(imgname) {
      try {
        // 파일 이름으로 찾기
        var file = await firebaseAdmin.storage().bucket().file(imgname);
    
        // 서명된 링크 기한 지정
        const config = {
          action: "read",
          expires: "03-17-2030"
        };
        
        // 링크 얻기
        var url = await file.getSignedUrl(config);
    
        return url;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return null;
      }
    }
    
    // 마찬가지로 이미지 이름으로 링크를 받아오지만 매우 짧은 url 링크이며 기한이 없는 링크.
    // 단, 차후 구글의 지원에 따라 이 링크는 무쓸모가 될 가능성이 있다.
    async function getImageShortLink(imgname) {
      try{
        // 파일 이름으로 찾기
        //var file = await firebaseAdmin.storage().bucket().file(imgname);
    
        // 이미지 링크 제작
        var link = "https://firebasestorage.googleapis.com/v0/b/" + 
        firebaseConfig.storageBucket + "/o/" + encodeURIComponent(imgname) + 
        "?alt=media";
    
        return link;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return null;
      }
    }
    
    /* GET home page. */
    // 테스트용
    router.get('/', function (req, res, next) {
      res.render('index', { title: '운영툴 Test' });
    });
    
    /* '/:id' 와일드카드 제외 부분 */
    // client 에서 보낸 데이터를 저장한다.
    router.post('/save', (req, res) => {
      const work = async () => {
        try {
          // client 가 보낸 userdata
          var userData = req.body;
    
          db.collection('userData').doc(userData.uid).set(userData)
          .then((snapshot) => {
            // 데이터 입력에 성공하면..
            console.log(userData.uid, " DB 입력 완료");
            res.send(true);
          })
          .catch((err) => {
            throw err;
          });
        } catch (err) {
          console.log(err);
          res.send(false);
          return;
        }
      }
    
      work();
    });
    
    // 로그인 화면
    router.get('/login', (req, res) => {
      res.render('manage/login');
      return;
    });
    
    // 로그아웃 처리
    router.get('/logout', async (req, res) => {
      console.log(req.cookies.session, " 로그아웃 처리");
      // Cookie 삭제
      res.clearCookie('__session');
      res.redirect('login');
    });
    
    // 세션 로그인 처리 부분
    router.post('/sessionLogin', async (req, res) => {
      try {
        // Get the ID token passed
        const idToken = req.body.idToken;
    
        // 5일의 유효기간 설정
        const expiresIn = 60 * 60 * 24 * 5 * 1000;
    
        // Create the session cookie. This will also verify the ID token in the process.
        // The session cookie will have the same claims as the ID token.
        // To only allow session cookie setting on recent sign-in, auth_time in ID token
        // can be checked to ensure user was recently signed in before creating a session cookie.
        const sessionCookie = await firebaseAdmin.auth().createSessionCookie(idToken, { expiresIn });
        // Set cookie policy for session cookie.
        // httpOnly : Cross-site 스크립팅 공격을 방지하기 위해, HttpOnly 쿠키는 JavaScript의 Document.cookie
        // API에 접근할 수 없으며 서버에게 전송되기만 합니다.
        // 로컬에서 테스트 하기 위해서는 httpOnly: true, secure: false 를 설정해야한다.
        // Secure : HTTPS 프로토콜 상에서 암호화된(encrypted) 요청일 경우에만 전송된다. 본질적으로 안전하지 않고
        // 이 플래그가 실질적인 보안을 제공하지 않기 때문입니다.
        const options = { maxAge: expiresIn, httpOnly: true, secure: false };
        res.cookie('__session', sessionCookie, options);
        //res.end(JSON.stringify({status: 'success'}));
        //res.redirect(307, 'top');
        res.redirect('top');
    
        return;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return;
      }
    });
    
    // 어딜 접속하든 무조건 로그인 체크
    // all 은 get, post 요청을 모두 처리가능하다.
    // 와일드카드 체크
    // 와일드카드는 자신보다 아래에 위치한 메서드들의 요청보다 우선시되며 모든 요청에 앞서 먼저 처리된다.
    router.all('/:id', async (req, res, next) => {
      // 이 메서드보다 아래인 메서드들은 일단 이 곳을 거쳐간다.
      var result = await checkCookie(req, res);
      if (result) {
        //console.log("로그인 상태..");
    
        // 원래 가려던 url에 
        next();
        return;
      } else {
        console.log(req.params, " 로그인 안상태");
        res.redirect('login');
    
        return;
      }
    });
    
    
    // 메인 화면
    router.get('/top', (req, res) => {
      const work = async () => {
        //var a = await getImageLongLink("manageTool/apple.png");
        //var b = await getImageShortLink("manageTool/apple.png");
        res.render('manage/top', { title: "Hello!" });
      }
    
      work();
    });
    
    // 모든 유저 데이터의 데이터를 불러온다.
    router.post('/userlist', (req, res) => {
      const work = async () => {
    
        // 모든 유저 정보를 가져온다.
        firebaseAdmin.firestore().collection('userData').orderBy("createDate", "desc").get()
          .then((snapshot) => {
            console.log("유저 수 : ", snapshot.size);
    
            var rows = [];
            snapshot.forEach((doc) => {
              var data = doc.data();
    
              data.createDate = new Date(data.createDate);
              rows.push(data);
            });
    
            res.render('manage/userlist', { rows: rows });
            return;
          })
          .catch((err) => {
            console.log(err);
          });
      }
    
      work();
    });
    
    // 특정 유저 데이터를 json으로 받아온 후 화면에 보여준다.
    router.get('/userlistdetail', (req, res) => {
      const work = async () => {
        console.log(req.query.uid, " 유저 데이터 조회");
    
        db.collection('userData').doc(req.query.uid).get()
          .then((snapshot) => {
            var user = snapshot.data();
            res.render('manage/userlistdetail', { data: user });
          })
          .catch((err) => {
            console.log(err);
            return;
          });
      }
    
      work();
    });
    
    module.exports = router;

     

    새로 등록한 라우터 파일(manage.js)를 index.js 에 새로운 미들웨어로 추가해줘야한다.

    index.js 파일은 functions 폴더에 있다.

    functions/index.js

    // 다른 미들웨어들 선언
    ...
    ...
    const functions = require('firebase-functions'); 
    
    // 다른 미들웨어들 선언 위치 아래에 선언하면 된다. 
    // 라우터 파일을 불러온다. manage.js 를 불러온다. 
    var manageRouter = require('./manage'); 
    
    // app 선언 
    var app = express(); 
    
    // app 선언 이후에 입력 
    // /manage 라는 신호가 들어오면 manageRouter 로 넘겨준다. 
    app.use('/manage', manageRouter);
    

    manage.js 은 인증, 세션쿠키, 스토리지, DB 를 활용하고 있다.

    위에서 부터 해석하자면...

     

    미들웨어 선언 부분

    // express 
    var express = require('express');
    // 쿠키 사용을 도와주는 노드 모듈
    var cookieParser = require('cookie-parser');
    
    // 로컬 테스트용
    //var jsdom = require('../functions/node_modules/jsdom');
    // 배포할때는 아래껄 사용해야한다.
    var jsdom = require('jsdom');
    const { JSDOM } = jsdom;
    const { window } = new JSDOM();
    const { document } = (new JSDOM('')).window;
    global.document = document;
    
    var router = express.Router();

    expresscookie-parserjsdom 3개의 미들웨어를 사용 설정한다.

    express 는 node.js를 위한 웹 프레임워크의 하나로 자유-오픈 소스 소프트웨어이다. 사실상 표준 웹 프레임워크라고 불리울 정도로 많은 개발자들이 사용중이다.

    cookie-parser 는 요청된 쿠키를 쉽게 추출할 수 있도록 도와주는 미들웨어이다.

    jsdom 은 웹 브라우저의 하위 집합들을 편하게 사용하는 것을 도와주는 미들웨어이다. 원래라면 

    express.Router() 클래스를 사용하여 모듈식 마운팅 가능한 핸들러를 만들어준다. router 인스턴스는 완전한 미들웨어이자 라우팅 시스템이다. 미니 앱이라고 불리는 경우가 많다. manage 미니 앱이 만들어진 것이다.

     

    Firebase SDK들 초기화 부분

    // firebase Admin 선언
    const admin = require('firebase-admin');
    
    // firebase Admin 초기화
    const firebaseAdmin = admin.initializeApp({
      credential: admin.credential.cert({
        firebase Admin SDK 키값 넣는 자리
      }),
    });
    
    // storageBucket 주소값을 넣어준다.
    const firebaseConfig = {
      storageBucket: "",
    };
    
    let db = firebaseAdmin.firestore();

    이 부분은 Firebase SDK 들을 초기화 시켜주는 부분이다.

    위에서 저장한 Firebase Admin SDK 와 Firebase SDK 를 이용하여 값을 채워주면 된다.

    firebase admin sdk 의 키값은 

    // firebase Admin 초기화
    const firebaseAdmin = admin.initializeApp({
      credential: admin.credential.cert({
        "type": "",
        "project_id": "",
        "private_key_id": "",
        "private_key": "",
        "client_email": "",
        "client_id": "",
        "auth_uri": "",
        "token_uri": "",
        "auth_provider_x509_cert_url": "",
        "client_x509_cert_url": ""
      }),
      databaseURL: "",
      storageBucket: ""
    });

     요렇게 넣어주면 되며 databaseURL, storageBucket 은 원래 admin sdk 에 없는 키값으로 Firebase SDK 에서 구하면 된다.

     

    Firebase SDK의 키값은

    const firebaseConfig = {
      apiKey: "AIzaSyDwwDzDxe-uDsJI1J-nWdkvkqKWH8g_EE4",
      authDomain: "unity3dfirebase.firebaseapp.com",
      databaseURL: "https://unity3dfirebase.firebaseio.com",
      projectId: "unity3dfirebase",
      storageBucket: "unity3dfirebase.appspot.com",
      messagingSenderId: "929927941880",
      appId: "1:929927941880:web:8b132964db0ffed0f1750d",
      measurementId: "G-V6YE82FLGD"
    };

    요렇게 넣어주면 된다.  Firebase SDK에 들어있는 키값들의 경우 사실상 공개키와 비슷하기 때문에 사용자들에게 공개되도 상관 없다.

     

    쿠키파서 초기화 부분

    // 쿠키파서 초기화
    router.use(cookieParser());
    
    // 쿠키가 있는지 체크
    async function checkCookie(req, res) {
      const work = async () => {
        try {
          // __session 이라 선언한 쿠키를 가지고 온다.
          const sessionCookie = req.cookies.__session || '';
    
          // Verify the session cookie. In this case an additional check is added to detect
          // if the user's Firebase session was revoked, user deleted/disabled, etc.
          await firebaseAdmin.auth().verifySessionCookie(sessionCookie.toString(), true /** checkRevoked */);
    
          return true;
        } catch (err) {
          console.log(err);
    
          return false;
        }
      }
    
      return await work();
    }

    router.use(cookieParser()); 를 사용하여 쿠키파서를 사용한다고 선언하는 동시에 cookieParser를 초기화 해주고 있다.

    Firebase Auth 를 사용하는 웹 사이트에서 세션 쿠키를 서버 측에서 관리할 수 있다. 일명 Firebase 세션 쿠키 관리 라고 말한다.

    checkCookie 비동기 함수는 firebaseAdmin SDK를 이용하여 세션 쿠키를 인증 및 권한 확인을 해주는 함수이다. 인증 도중 실패할 경우 오류를 발생 시켜 false 를 리턴한다.

     

    Firebase Storage 에서 이미지 링크를 가져오는 2가지 방법

    // 이미지 이름으로 Firebase Storage 에서 이미지 다운로드 링크 받아오기
    // 서명 되어 있고 기한이 있는 링크
    async function getImageLongLink(imgname) {
      try {
        // 파일 이름으로 찾기
        var file = await firebaseAdmin.storage().bucket().file(imgname);
    
        // 서명된 링크 기한 지정
        const config = {
          action: "read",
          expires: "03-17-2030"
        };
        
        // 링크 얻기
        var url = await file.getSignedUrl(config);
    
        return url;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return null;
      }
    }
    
    // 마찬가지로 이미지 이름으로 링크를 받아오지만 매우 짧은 url 링크이며 기한이 없는 링크.
    // 단, 차후 구글의 지원에 따라 이 링크는 무쓸모가 될 가능성이 있다.
    async function getImageShortLink(imgname) {
      try{
        // 파일 이름으로 찾기
        //var file = await firebaseAdmin.storage().bucket().file(imgname);
    
        // 이미지 링크 제작
        var link = "https://firebasestorage.googleapis.com/v0/b/" + 
        firebaseConfig.storageBucket + "/o/" + encodeURIComponent(imgname) + 
        "?alt=media";
    
        return link;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return null;
      }
    }

    Firebase Storage 에서 이미지 링크를 받아오는 방법은 2가지 있다.

    파일 이름으로 이미지를 찾은 후 기한이 있고 매우 긴 링크를 받아오는 방법(getImageLongLink)과 마찬가지로 하드코딩으로 만들어진 짧은 기한 없는 링크(getImageShortLink)를 받아오는 방법이다.

    짧은 링크 방법의 경우 간편하지만 차후 구글의 지원에 따라 링크가 작동이 안 될 가능성이 있다. 2번째 짧은 링크는 Firebase Storage 에서 만들어지는 링크이다. apple.png 옆의 사각형을 눌렀을때 나오는 링크에서 뒤에 token 주소를 뺀 것이다.

     

    와일드 카드 제외 부분

    /* '/:id' 와일드카드 제외 부분 */
    // client 에서 보낸 데이터를 저장한다.
    router.post('/save', (req, res) => {
      const work = async () => {
        try {
          // client 가 보낸 userdata
          var userData = req.body;
    
          db.collection('userData').doc(userData.uid).set(userData)
          .then((snapshot) => {
            // 데이터 입력에 성공하면..
            console.log(userData.uid, " DB 입력 완료");
            res.send(true);
          })
          .catch((err) => {
            throw err;
          });
        } catch (err) {
          console.log(err);
          res.send(false);
          return;
        }
      }
    
      work();
    });
    
    
    // 로그인 화면
    router.get('/login', (req, res) => {
      res.render('manage/login');
      return;
    });
    
    // 로그아웃 처리
    router.get('/logout', async (req, res) => {
      console.log(req.cookies.session, " 로그아웃 처리");
      // Cookie 삭제
      res.clearCookie('__session');
      res.redirect('login');
    });
    
    // 세션 로그인 처리 부분
    router.post('/sessionLogin', async (req, res) => {
      try {
        // Get the ID token passed
        const idToken = req.body.idToken;
    
        // 5일의 유효기간 설정
        const expiresIn = 60 * 60 * 24 * 5 * 1000;
    
        // Create the session cookie. This will also verify the ID token in the process.
        // The session cookie will have the same claims as the ID token.
        // To only allow session cookie setting on recent sign-in, auth_time in ID token
        // can be checked to ensure user was recently signed in before creating a session cookie.
        const sessionCookie = await firebaseAdmin.auth().createSessionCookie(idToken, { expiresIn });
        // Set cookie policy for session cookie.
        // httpOnly : Cross-site 스크립팅 공격을 방지하기 위해, HttpOnly 쿠키는 JavaScript의 Document.cookie
        // API에 접근할 수 없으며 서버에게 전송되기만 합니다.
        // 로컬에서 테스트 하기 위해서는 httpOnly: true, secure: false 를 설정해야한다.
        // Secure : HTTPS 프로토콜 상에서 암호화된(encrypted) 요청일 경우에만 전송된다. 본질적으로 안전하지 않고
        // 이 플래그가 실질적인 보안을 제공하지 않기 때문입니다.
        const options = { maxAge: expiresIn, httpOnly: true, secure: false };
        res.cookie('__session', sessionCookie, options);
        //res.end(JSON.stringify({status: 'success'}));
        //res.redirect(307, 'top');
        res.redirect('top');
    
        return;
      } catch (err) {
        console.log(err);
    
        res.status(401).send('UNAUTHORIZED REQUEST!');
        return;
      }
    });

    다음에 나올 /:id 와일드카드에서 제외 되는 라우팅 메소드들이며 DB 저장과 로그인, 로그아웃을 처리해주는 부분이다.

     

    router.post('/save') 의 경우 밑에서 만들 Client 가 보내주는 데이터를 Firestore 에 저장하는 역할을 한다.

    자세한 설명은 https://seonbicode.tistory.com/47 을 참고하자.

     

    router.post('/sessionLogin') 의 경우 post 로 차후에 나오는 login.ejs 에서 보내온 idToken 으로 세션쿠키를 만들어준다. 

    const sessionCookie = await firebaseAdmin.auth().createSessionCookie(idToken, { expiresIn });

    가장 중요한 Firebase Admin SDK 를 사용하여 idToken 과 유효기간을 사용하여 sessionCookie를 만들어준다.

     

    추가로 중요한 애플리케이션인 경우 세션 쿠키를 발행하기 전에 auth_time을 확인하여 ID 토큰 도난 시의 공격 기간을 최소화하는 방법이 있다고 한다.

    admin.auth().verifyIdToken(idToken)
      .then((decodedIdToken) => {
        // Only process if the user just signed in in the last 5 minutes.
       if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
          // Create session cookie and set it.
          return admin.auth().createSessionCookie(idToken, {expiresIn});
        }
        // A user that was not recently signed in is trying to set a session cookie.
       // To guard against ID token theft, require re-authentication.
        res.status(401).send('Recent sign in required!');
      });

     

    option 은 maxage 와 httpOnly 와 secure 에 대해서 선택할 수 있다.

    HttpOnly - 자바스크립트의 document.cookie를 이용해서 쿠키에 접속하는 것을 막는 옵션이다. 쿠키를 훔쳐가는 행위를 막기 위한 옵션

    Secure - 웹브라우저와 웹서버가 https로 통신하는 경우만 웹브라우저가 쿠키를 서버로 전송하는 옵션

     

    firebase serve 옵션을 사용하여 로컬 테스트를 하는 경우 Secure 옵션을 false 로 설정해줘야하며 firebase deploy 로 서버를 배포해줄 때는 Secure 옵션을 true 로 설정해줘야한다.

     

    그리고 Firebase Hosting 에서 세션쿠키를 사용하기 위해서는 세션쿠키 이름을 __session 으로 지정해줘야한다.

     

    와일드 카드

    // 어딜 접속하든 무조건 로그인 체크
    // all 은 get, post 요청을 모두 처리가능하다.
    // 와일드카드 체크
    // 와일드카드는 자신보다 아래에 위치한 메서드들의 요청보다 우선시되며 모든 요청에 앞서 먼저 처리된다.
    router.all('/:id', async (req, res, next) => {
      // 이 메서드보다 아래인 메서드들은 일단 이 곳을 거쳐간다.
      var result = await checkCookie(req, res);
      if (result) {
        //console.log("로그인 상태..");
    
        // 원래 가려던 url에 
        next();
        return;
      } else {
        console.log(req.params, " 로그인 안상태");
        res.redirect('login');
    
        return;
      }
    });

    router.all 의 경우 get, post 신호에 대해 모두 처리가능하다.

    와일드카드 제외 부분을 제외한 라우터의 모든 신호는 일단 와일드 카드를 거친다. 와일드 카드에서는 세션쿠키를 검증 후 true면 next() 로 원래 가려던 라우팅 메소드로 이동시켜준다. false 의 경우 다시 login 화면으로 돌아가게 한다.

     

    라우팅 메소드

    // 메인 화면
    router.get('/top', (req, res) => {
      const work = async () => {
        //var a = await getImageLongLink("manageTool/apple.png");
        //var b = await getImageShortLink("manageTool/apple.png");
        res.render('manage/top', { title: "Hello!" });
      }
    
      work();
    });
    
    // 모든 유저 데이터의 데이터를 불러온다.
    router.post('/userlist', (req, res) => {
      const work = async () => {
    
        // 모든 유저 정보를 가져온다.
        firebaseAdmin.firestore().collection('userData').orderBy("createDate", "desc").get()
          .then((snapshot) => {
            console.log("유저 수 : ", snapshot.size);
    
            var rows = [];
            snapshot.forEach((doc) => {
              var data = doc.data();
    
              data.createDate = new Date(data.createDate);
              rows.push(data);
            });
    
            res.render('manage/userlist', { rows: rows });
            return;
          })
          .catch((err) => {
            console.log(err);
          });
      }
    
      work();
    });
    
    // 특정 유저 데이터를 json으로 받아온 후 화면에 보여준다.
    router.get('/userlistdetail', (req, res) => {
      const work = async () => {
        console.log(req.query.uid, " 유저 데이터 조회");
    
        db.collection('userData').doc(req.query.uid).get()
          .then((snapshot) => {
            var user = snapshot.data();
            res.render('manage/userlistdetail', { data: user });
          })
          .catch((err) => {
            console.log(err);
            return;
          });
      }
    
      work();
    });

    위에서부터 메인화면, 유저 리스트, 유저 리스트 디테일 화면에 대한 라우팅 메소드들이다.

     

    유저 리스트와 유저 리스트 디테일의 경우 Firestore 에서 데이터를 가져온다.

     

    모듈 생성

    module.exports = router;

    최종적으로 module.exports 를 이용하여 모듈 생성을 한다.

    manage router module 이 생성 된것이다.

     

     

    Server View

    웹 사이트 뷰를 구현해줄 4개의 ejs(node.js html) 파일과 1개의 모듈을 추가해줘야한다.

    그리고 이 글의 웹사이트는 Bootstrap 을 사용하여 웹사이트 디자인을 사용했다. https://getbootstrap.com/docs/4.4/examples/jumbotron/

     

     

    head.ejs

    parts.zip
    0.00MB

    parts.zip 를 압축해제하여 나온 parts 폴더를 프로젝트/functions/view 폴더에 넣어주면 된다.

    공통적으로 사용하는 html 을 모듈화 한 것이다.

     

    login.ejs

    login.ejs 는 사용자가 로그인하는 화면을 구성한다.

    firebase 인증을 처리해주는 스크립트를 포함하고 있다.

    login.ejs
    0.00MB
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
    
        <!-- bootstrap css 파일 참조 -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
            integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-left">
            <a class="navbar-brand" href="#">Manager Tool</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01"
                aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
        </nav>
    </head>
    
    <body>
        <!-- Firebase CDN -->
        <script src="https://www.gstatic.com/firebasejs/7.5.2/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/7.5.2/firebase-analytics.js"></script>
        <script src="https://www.gstatic.com/firebasejs/7.5.2/firebase-auth.js"></script>
    
        <div class="jumbotron">
            <!--<form name="login" action="login_" method="POST">-->
                <fieldset>
                    <legend>로그인 화면</legend>
                    <div class="form-group">
                        <label for="exampleInputEmail1">Email address</label>
                        <!-- Email 입력 받는 곳 -->
                        <input type="email" name="email" class="form-control" id="inputEmail1" aria-describedby="emailHelp"
                            placeholder="Enter email">
                    </div>
                    <div class="form-group">
                        <label for="exampleInputPassword1">Password</label>
                        <!-- Password 입력 받는 곳 -->
                        <input type="password" name="pw" class="form-control" id="inputPassword1" placeholder="Password">
                    </div>
                </fieldset>
                <!--<button class="btn btn-primary my-2 my-sm-0" type="submit">Sign in</button>-->
                <button class="btn btn-primary my-2 my-sm-0" id="loginBtn">Sign in</button>
            <!--</form>-->
        </div>
    </body>
    
    <script>
        /* Firebase SDK 초기화 */
        firebase.initializeApp({
            apiKey: "",
            authDomain: ""
        });
    
        /* 로그인 버튼에 새로운 이벤트 리스너를 달아준다. */
        loginBtn.addEventListener("click", e => {
            var emailField = document.getElementById("inputEmail1");
            var pwField = document.getElementById("inputPassword1");
            var loginBtn = document.getElementById("loginBtn");
    
            console.log(emailField.value, pwField.value);
    
            const email = emailField.value;
            const pw = pwField.value;
    
            if (email != "test@gmail.com" || pw.length < 5) {
                console.log("id or pw 를 다시 입력하세요.");
                return;
            }
    
            /* Firebase Auth Email 인증 처리 */
            const signInPromise = firebase.auth().signInWithEmailAndPassword(email, pw);
            signInPromise.catch(e => {
                console.log("login Error: ", e.message);
            });
            return signInPromise.then(() => {
                console.log("Signed in + ", firebase.auth().currentUser.uid);
                /* Firebase SDK 에서 IdToken 을 받아온다. 
                    단 이 부분은 이메일 인증을 통과했을 때만 들어온다. */
                return firebase.auth().currentUser.getIdToken().then(idToken => {
                    //console.log("User ID Token: ", idToken);
    
                    var form = document.createElement("form");
                    form.setAttribute('method', 'POST');
                    form.setAttribute('action', 'sessionlogin');
                    document.charset = "utf-8";
    
                    var hiddenField = document.createElement('input');
                    hiddenField.setAttribute("type", "hidden");
                    hiddenField.setAttribute("name", "idToken");
                    hiddenField.setAttribute("value", idToken);
                    form.appendChild(hiddenField);
                    document.body.appendChild(form);
                    form.submit();
                }).catch(err => {
                    console.log(err);
                })
            })
        });
    </script>
    </html>

    Firebase SDK 초기화 부분은 아까 다운 받아둔 Firebase SDK 에서 apiKey 와 authDomain 찾아 키값을 채워주면 된다.

     

    사용자가 입력한 이메일이 test@gmail.com 일 경우에만 Firebase 이메일 검증으로 진입할 수 있으며 아래 함수를 이용하여 Firebase 이메일 인증을 검증한다.

    firebase.auth().signInWithEmailAndPassword(email, pw);

    Firebase 인증에 test@gmail.com 이 없을 경우 아무 반응이 없다.

     

    아래 함수를 통해 Firebase 인증을 통과한 유저의 아이디 토큰을 받아온다.

    firebase.auth().currentUser.getIdToken()

     

    얻은 아이디 토큰을 잘 포장 (form.setAttribute...) 하여 menage.js 의 sessionlogin 으로 Post 한다.

    router.post('/sessionLogin', async (req, res) => {
      try {
        // Get the ID token passed
        const idToken = req.body.idToken;

    sessionlogin 에서 idToken 을 받는 모습이다...

    express 에서는 GET 으로 온 데이터는 req.query 로 받고 POST 으로 온 데이터는 req.body 로 받는다.
    req.body 로 편하게 JSON,txt등 여러 데이터를 편하게 받을 수 있는 이유는 express 에는 기본적으로 body-parser 모듈의 기능을 일부 포함하고 있기 때문이다.
    index.js 의 아래 코드에서 body-parser 기능 선언을 볼 수 있다.
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));

     

    top.ejs

    top.ejs
    0.00MB

     

    <!DOCTYPE html>
    <html>
        <head>
            <% include ../parts/head %>
    
        </head>
    
        <body>
                <div class="jumbotron">
                        <h1 class="display-3"><%= title %></h1>
                        <p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
                        <hr class="my-4">
                        <p>It uses utility classes for typography and spacing to space content out within the larger container.</p>
                        <p class="lead">
                          <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
                        </p>
                      </div>
        </body>
    </html>

    로그인 화면에서 로그인 완료 시 무조건 처음 진입하는 화면의 ejs 이다.

    manage.js 의 sessionLogin 라우팅 메소드에서 sessionCookie를 성공적으로 제작했다면 res.redirect('top'); 으로 top.ejs 를 불러준다.

     

    userlist.ejs

    userlist.ejs
    0.00MB
    <!DOCTYPE html>
    <html>
    
    <head>
      <% include ../parts/head %>
    
    </head>
    
    <body>
      <% const firebaseConfig = {
            storageBucket: ""
          }; %>
      <div class="jumbotron">
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">UID</th>
              <th scope="col">NickName</th>
              <th scope="col">CreateDate</th>
              <th scope="col">OS</th>
              <th scope="col">Button</th>
            </tr>
          </thead>
          <tbody>
            <% var count = 0; %>
            <% rows.forEach((val) => { %>
            <% 
              if((count+1)%2==0) { 
            %>
            <tr>
            <% } else { %>
            <tr class="table-primary">
            <% } count++; %>
              <th scope="row"><%= val.uid %></th>
              <td><%= val.nickname %></td>
              <td><%= val.createDate %></td>
              <% var osStr = val.deviceOS.substring(0, val.deviceOS.indexOf(' ')); %>
              <% 
                var link = "";
                switch(osStr)
                {
                  case "Android":
                    link = "https://firebasestorage.googleapis.com/v0/b/" + 
                    firebaseConfig.storageBucket + "/o/" + encodeURIComponent("manageTool/android.png") + 
                    "?alt=media";
                    break;
    
                  case "Mac":
                  link = "https://firebasestorage.googleapis.com/v0/b/" + 
                  firebaseConfig.storageBucket + "/o/" + encodeURIComponent("manageTool/apple.png") + 
                  "?alt=media";
                    break;
    
                  case "Windows":
                  link = "https://firebasestorage.googleapis.com/v0/b/" + 
                  firebaseConfig.storageBucket + "/o/" + encodeURIComponent("manageTool/windows.png") + 
                  "?alt=media";
                    break;
                } %>
              <td><img src="<%=link%>" width="50" height="50"></td>
              <% var url = "/manage/userlistdetail?uid=" + val.uid; %>
              <td><button class="btn btn-primary my-2 my-sm-0" type="submit" 
                onclick="location.href='<%=url%>'">Detail</button></td>
            </tr>
            <% }) %>
          </tbody>
        </table>
      </div>
    </body>
    
    </html>

    userlist.ejs 는 Firebase 기능인 Firestore 와 storage 에서 데이터와 이미지를 불러와 유저 목록을 구성한다.

     

    router.post('/userlist', (req, res) => {
      const work = async () => {
    
        // 모든 유저 정보를 가져온다.
        firebaseAdmin.firestore().collection('userData').orderBy("createDate", "desc").get()
          .then((snapshot) => {
            console.log("유저 수 : ", snapshot.size);
    
            var rows = [];
            snapshot.forEach((doc) => {
              var data = doc.data();
    
              data.createDate = new Date(data.createDate);
              rows.push(data);
            });
    
            res.render('manage/userlist', { rows: rows });
            return;
          })
          .catch((err) => {
            console.log(err);
          });
      }
    
      work();
    });

    manage.js 에서 userlist 라우팅 메소드 에서 보내온 유저 데이터 배열( rows )을 이용하여 테이블을 가공한다.

    rows.forEach 문을 통해 하나의 데이터를 val 로 꺼낸 후 deviceOS 얻어 거기에 맞는 

    link = "https://firebasestorage.googleapis.com/v0/b/" + 
            firebaseConfig.storageBucket + 
            "/o/" + 
            encodeURIComponent("manageTool/android.png") + 
            "?alt=media";

    manage.js 의 getImageShortLink() 함수 안에 있던 것을 약간 수정을 가한 것이다.

    Firebase Storage 의 manageTool 폴더에 업로드한 android.png 의 고정 다운로드 링크를 만들어주는 코드이다.

     

    <% var url = "/manage/userlistdetail?uid=" + val.uid; %>
    <td><button class="btn btn-primary my-2 my-sm-0" type="submit" 
    onclick="location.href='<%=url%>'">Detail</button></td>

    Detail 버튼을 만들어주고 Detail 버튼을 눌렀을때 GET 방식으로 manage.js 에 있는 userlistdetail 라우팅 메소드에 주소값으로 데이터를 보낸다.

    router.get('/userlistdetail', (req, res) => {
      const work = async () => {
        console.log(req.query.uid, " 유저 데이터 조회");

     

    userlistdetail.ejs

    userlistdetail.ejs
    0.00MB
    <!DOCTYPE html>
    <html>
    
    <head>
      <% include ../parts/head %>
    
    </head>
    
    <body>
      <div class="jumbotron">
        <div class="page-header" id="banner">
          <div class="row">
            <div class="col-lg-8 col-md-7 col-sm-6">
              <h1>UserDetail</h1>
              <p class="lead">유저의 자세한 정보</p>
            </div>
          </div>
        </div>
        <div class="bs-docs-section">
          <form>
            <fieldset>
              <legend>유저 생성 정보</legend>
              <div class="form-group row">
                <label for="staticUid" class="col-sm-2 col-form-label">UID</label>
                <div class="col-sm-10">
                  <input type="text" readonly="" class="form-control-plaintext" id="staticUid"
                    value="<%=data.uid%>">
                </div>
              </div>
              <div class="form-group row">
                  <label for="staticNickName" class="col-sm-2 col-form-label">Nick Name</label>
                  <div class="col-sm-10">
                    <input type="text" readonly="" class="form-control-plaintext" id="staticNickName"
                      value="<%=data.nickname%>">
                  </div>
                </div>
              <div class="form-group row">
                <label for="staticCreateDate" class="col-sm-2 col-form-label">Create Date</label>
                <div class="col-sm-10">
                  <% var date = new Date(data.createDate); %>
                  <input type="text" readonly="" class="form-control-plaintext" id="staticCreateDate"
                    value="<%=date%>">
                </div>
              </div>
              <div class="form-group row">
                  <label for="staticLoginType" class="col-sm-2 col-form-label">Login Type</label>
                  <div class="col-sm-10">
                    <% if(data.loginType === 1) { %>
                    <input type="text" readonly="" class="form-control-plaintext" id="staticLoginType"
                      value="익명 로그인">
                    <% } else if(data.loginType === 2) { %>
                    <input type="text" readonly="" class="form-control-plaintext" id="staticLoginType"
                      value="이메일 로그인">
                    <label for="staticEmail" class="col-sm-2 col-form-label">Email</label>
                    <input type="text" readonly="" class="form-control-plaintext" id="staticEmail"
                      value="<%=data.email%>">
                    <% } else if(data.loginType === 3) { %>
                    <input type="text" readonly="" class="form-control-plaintext" id="staticLoginType"
                      value="구글 로그인">
                    <% } %>
                  </div>
                </div>
            </fieldset>
          </form>
          <form>
            <fieldset>
              <legend>디바이스 정보</legend>
              <div class="form-group row">
                <label for="staticDeviceOS" class="col-sm-2 col-form-label">Device OS</label>
                <div class="col-sm-10">
                  <input type="text" readonly="" class="form-control-plaintext" id="staticDeviceOS"
                    value="<%=data.deviceOS%>">
                </div>
              </div>
              <div class="form-group row">
                <label for="staticDeviceModel" class="col-sm-2 col-form-label">Device Model</label>
                <div class="col-sm-10">
                  <input type="text" readonly="" class="form-control-plaintext" id="staticDeviceModel"
                    value="<%=data.deviceModel%>">
                </div>
              </div>
              <div class="form-group row">
                  <label for="staticDeviceName" class="col-sm-2 col-form-label">Device Name</label>
                  <div class="col-sm-10">
                    <input type="text" readonly="" class="form-control-plaintext" id="staticDeviceName"
                      value="<%=data.deviceName%>">
                  </div>
                </div>
            </fieldset>
          </form>
        </div>
        <td><button class="btn btn-primary my-2 my-sm-0" type="button" 
          onclick="history.go(-1)">back</button></td>
      </div>
    
    </body>
    
    </html>

    userlist 화면에서 Detail 버튼을 눌렀을때 진입하는 userdetail 화면을 구성해주는 userlistdetail.ejs 이다.

     

    manage.js 의 userlistdetail 라우팅 메소드에서는 GET 방식으로 받은 uid 를 이용하여 Firestore 에서 특정 유저의 데이터를 얻고 이 data 를 userlistdetail.ejs 에 보내준다.

     

    manage.js 의 userlistdetail 라우팅 메소드에서 보내는 모습

    db.collection('userData').doc(req.query.uid).get()
          .then((snapshot) => {
            var user = snapshot.data();
            res.render('manage/userlistdetail', { data: user });
          })
          .catch((err) => {
            console.log(err);
            return;
          });

     

    사용하는 모습

    <input type="text" readonly="" class="form-control-plaintext" id="staticUid"
    value="<%=data.uid%>">

     

     

     

    여기까지 만들었다면 기본적인 운영툴 모습은 갖추었지만 Firebase Cloud Firestore 에 어떠한 데이터도 없기 때문에 아무런 데이터도 받아오지 못한다.

     

    Firestore 에 데이터를 채워줄 수 있는 간단한 클라이언트를 제작하도록 한다.

     

     

    Client

    Client 는 Unity3d를 이용하여 제작한다.

     

    Firestore에 저장할 데이터 구조는 아래와 같다.

    public class userLoginData
    {
    	public enum LoginType
    	{
    		None = 0,
    		anony = 1,
    		email = 2,
    		google = 3
    	}
    	public string nickname;
    	public LoginType loginType;
    	public string uid;
    	public string email;
    	public string pw;
    	public string deviceModel;
    	public string deviceName;
    	public DeviceType deviceType;
    	public string deviceOS;
    	public ulong createDate;
    }

     

    https://seonbicode.tistory.com/45#Client

    위 링크를 참고하여 login.cs 를 만든다. 

    이것을 베이스로 아래를 제작한다.

    login.cs
    0.01MB
    using Google;
    using Firebase.Auth;
    
    using UnityEngine;
    using System.Collections;
    using System.Threading.Tasks;
    using UnityEngine.Networking;
    
    public class login : MonoBehaviour
    {
        // Firestore 에 저장할 user data
        public class userLoginData
        {
            public enum LoginType
            {
                None = 0,
                anony = 1,
                email = 2,
                google = 3
            }
            public string nickname;
            public LoginType loginType;
            public string uid;
            public string email;
            public string pw;
            public string deviceModel;
            public string deviceName;
            public UnityEngine.DeviceType deviceType;
            public string deviceOS;
            public ulong createDate;
        }
    
        // Auth 용 instance
        FirebaseAuth auth = null;
    
        // 사용자 계정
        FirebaseUser user = null;
    
        // 기기 연동이 되어 있는 상태인지 체크한다.
        private bool signedIn = false;
    
        private void Awake()
        {
            // 초기화
            auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
    
            // 유저의 로그인 정보에 어떠한 변경점이 생기면 실행되게 이벤트를 걸어준다.
            auth.StateChanged += AuthStateChanged;
            //AuthStateChanged(this, null);
        }
    
        // 계정 로그인에 어떠한 변경점이 발생시 진입.
        void AuthStateChanged(object sender, System.EventArgs eventArgs)
        {
            if (auth.CurrentUser != user)
            {
                // 연동된 계정과 기기의 계정이 같다면 true를 리턴한다. 
                signedIn = user != auth.CurrentUser && auth.CurrentUser != null;
    
                if (!signedIn && user != null)
                {
                    UnityEngine.Debug.Log("Signed out " + user.UserId);
                }
    
                user = auth.CurrentUser;
    
                if (signedIn)
                {
                    UnityEngine.Debug.Log("Signed in " + user.UserId);
                }
            }
        }
    
        //////////////
        // 익명 로그인 //
        //////////////
        public void AnonyLogin()
        {
            // 익명 로그인 진행
            auth.SignInAnonymouslyAsync().ContinueWith(task =>
            {
                if (task.IsCanceled)
                {
                    Debug.LogError("SignInAnonymouslyAsync was canceled.");
                    return;
                }
                if (task.IsFaulted)
                {
                    Debug.LogError("SignInAnonymouslyAsync encountered an error: " + task.Exception);
                    return;
                }
    
                // 익명 로그인 연동 결과
                Firebase.Auth.FirebaseUser newUser = task.Result;
                Debug.LogFormat("User signed in successfully: {0} ({1})",
                    newUser.DisplayName, newUser.UserId);
    
                // 신규 유저 데이터 입력
                loginDataSave(userLoginData.LoginType.google, "testNickname");
            });
        }
    
        //////////////
        // 구글 로그인 //
        //////////////
        private void GoogleLoginProcessing()
        {
            if (GoogleSignIn.Configuration == null)
            {
                // 설정
                GoogleSignIn.Configuration = new GoogleSignInConfiguration
                {
                    RequestIdToken = true,
                    RequestEmail = true,
                    // Copy this value from the google-service.json file.
                    // oauth_client with type == 3
                    WebClientId = ""
                };
            }
    
            Task<GoogleSignInUser> signIn = GoogleSignIn.DefaultInstance.SignIn();
    
            TaskCompletionSource<FirebaseUser> signInCompleted = new TaskCompletionSource<FirebaseUser>();
    
            signIn.ContinueWith(task =>
            {
                if (task.IsCanceled)
                {
                    Debug.Log("Google Login task.IsCanceled");
                }
                else if (task.IsFaulted)
                {
                    Debug.Log("Google Login task.IsFaulted");
                }
                else
                {
                    Credential credential = Firebase.Auth.GoogleAuthProvider.GetCredential(((Task<GoogleSignInUser>)task).Result.IdToken, null);
                    auth.SignInWithCredentialAsync(credential).ContinueWith(authTask =>
                    {
                        if (authTask.IsCanceled)
                        {
                            signInCompleted.SetCanceled();
                            Debug.Log("Google Login authTask.IsCanceled");
                            return;
                        }
                        if (authTask.IsFaulted)
                        {
                            signInCompleted.SetException(authTask.Exception);
                            Debug.Log("Google Login authTask.IsFaulted");
                            return;
                        }
    
                        user = authTask.Result;
                        Debug.LogFormat("Google User signed in successfully: {0} ({1})", user.DisplayName, user.UserId);
    
                        // 신규 유저 데이터 입력
                        loginDataSave(userLoginData.LoginType.google, "testNickname");
                        return;
                    });
                }
            });
        }
    
        ////////////////
        // 이메일 로그인 //
        ////////////////
        public void EmailLogin()
        {
            var email = EmailCreatePanel.transform.Find("email").Find("Text").GetComponent<UnityEngine.UI.Text>().text;
            var pw = EmailCreatePanel.transform.Find("pw").Find("Text").GetComponent<UnityEngine.UI.Text>().text;
    
            if (email.Length < 1 || pw.Length < 1)
            {
                Debug.Log("이메일 ID 나 PW 가 비어있습니다.");
                return;
            }
    
            auth.CreateUserWithEmailAndPasswordAsync(email, pw).ContinueWith(task =>
            {
                if (task.IsCanceled)
                {
                    UnityEngine.Debug.LogError("CreateUserWithEmailAndPasswordAsync was canceled.");
                    return;
                }
                if (task.IsFaulted)
                {
                    UnityEngine.Debug.LogError("CreateUserWithEmailAndPasswordAsync encountered an error: " + task.Exception);
                    return;
                }
    
                // firebase email user create
                Firebase.Auth.FirebaseUser newUser = task.Result;
                UnityEngine.Debug.LogFormat("Firebase Email user created successfully: {0} ({1})", newUser.DisplayName, newUser.UserId);
    
                // 신규 유저 데이터 입력
                loginDataSave(userLoginData.LoginType.email, "testNickname", email, pw);
    
                return;
            });
        }
    
        // 연동 해제
        public void SignOut()
        {
            if (auth.CurrentUser != null)
                auth.SignOut();
        }
    
        // 연동 계정 삭제
        public void UserDelete()
        {
            if (auth.CurrentUser != null)
                auth.CurrentUser.DeleteAsync();
        }
    
        // 신규 유저 데이터 입력
        private void loginDataSave(userLoginData.LoginType loginType, string nickname = null, string email = null, string pw = null)
        {
            // 유저 데이터
            var newUser = new userLoginData();
    
            // DB에 저장될 유저데이터 초기화
            newUser.loginType = loginType;
            newUser.nickname = nickname;
            newUser.uid = user.UserId;
            newUser.email = email;
            newUser.pw = pw;
            newUser.deviceModel = SystemInfo.deviceModel;
            newUser.deviceName = SystemInfo.deviceName;
            newUser.deviceType = SystemInfo.deviceType;
            newUser.deviceOS = SystemInfo.operatingSystem;
            newUser.createDate = auth.CurrentUser.Metadata.CreationTimestamp;
            
            string json = JsonUtility.ToJson(newUser);
    
            // 위에 정리한 json을 서버에 보내 DB에 저장한다.
            // 새로운 유저에 대한 데이터를 DB에 보낸다.
            NewUserJsonDBSave(json);
        }
    
        // 새로 생성된 유저 데이터(json)을 서버에 저장한다.
        public void NewUserJsonDBSave(string json)
        {
            try
            {
                StartCoroutine(JsonDBSavePost("http://localhost:5000/manage/save", json));
            }
            catch (System.Exception err)
            {
                Debug.Log(err);
            }
        }
        private IEnumerator JsonDBSavePost(string url, string json)
        {
            using (var uwr = new UnityWebRequest(url, "POST"))
            {
                Debug.Log(json);
    
                byte[] jsonToSend = new System.Text.UTF8Encoding().GetBytes(json);
                uwr.uploadHandler = (UploadHandler)new UploadHandlerRaw(jsonToSend);
                uwr.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
                uwr.SetRequestHeader("Content-Type", "application/json");
    
                yield return uwr.SendWebRequest();
    
                if (uwr.isNetworkError)
                {
                    Debug.Log("Error While Sending: " + uwr.error);
                }
                else
                {
                    var receiptSave = bool.Parse(uwr.downloadHandler.text);
    
                    Debug.LogFormat("Received: ({0}) {1}", url, receiptSave);
                }
            }
        }
    }

     

    Unity3d 클라이언트에서 Firebase 인증 후 유저 데이터를 만들어 서버에 보내는 클라이언트 코드이다. 

    위의 코드를 그대로 사용하면 loginDataSave() 함수에서 NiceName을 무조건  "testNickName" 으로 받고 있으니 적당한 수정을 하여 사용하자.

     

    클라이언트 코드의 경우는..

    https://seonbicode.tistory.com/45

     

    [Firebase] Unity3d 유저 인증

    목차 Turorial 이름 버전 (2019-12-04 기준) Unity3d 2018.4.13f1 (LTS) Firebase Unity SDK 6.7.0 Unity3d 클라이언트에서 Firebase Unity SDK 만을 이용하여 Firebase 인증을 사용한다. Firebase Setting 이 글..

    seonbicode.tistory.com

    https://seonbicode.tistory.com/47 

     

    [Firebase] 서버에서 Database 사용하기

    목차 Turorial Firebase 에서 사용할 수 있는 DB는 2종류이다. RealTimeDatabase Cloud Firestore 둘다 NoSQL DB이며 실시간으로 데이터가 동기화된다. 새 프로젝트를 시작하는 단계의 개발자들은 Firebase DB들..

    seonbicode.tistory.com

    위 링크들에 자세하게 설명되어 있다.

     

     

    Connect

    위에서 제작한 Server를 작동시키고 Client 로 로그인을 진행하여 성공적으로 데이터를 주고받으면 아래와 같은 결과값이 Firestore 에 저장된다.

    Unity3d 에서 테스트를 하였기 때문에 MacOS 로 DB에 저장된다.  

     

    크롬 기준으로 로그인에 성공한다면 session cookie 파일이 생성된 것을 확인 할 수 있다.

     

    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기