로메오의 블로그

[Firebase] Authentication 로그인 - Google, Facebook, Email 본문

Frontend/Firebase

[Firebase] Authentication 로그인 - Google, Facebook, Email

romeoh 2019. 10. 3. 23:50
반응형

Firebase 목록

[FIREBASE] 호스팅 생성하고 배포하기

[FIREBASE] STORAGE에 사진 파일 올리기

[CHROME] INDEXEDDB 사용하기

 

회원가입을 받기위해서 Firebase > Authentication > 로그인 방법으로 이동합니다.

이메일/비밀번호 수정을 선택합니다.

사용 설정 하고 저장합니다.

Google 도 사용설정 하고 저장합니다.

Facebook 사용설정합니다.

OAuth redirection URI를 복사해 둡니다.

 

Facebook 개발자 센터

https://developers.facebook.com

Facebook 개발자 센터에서 회원가입 / 로그인 합니다.

앱 만들기를 클릭합니다.

 

Facebook 로그인 설정을 누릅니다.

 

좌측에 설정을 누르고 복사해둔 OAuth 리디렉션 URI를 붙여넣기 하고 저장합니다.

설정 > 기본설정에서 앱ID와 앱 시크릿 코드를 복사해서 Firebase에 붙여넣기 합니다.

Facebook은 현재 개발 중 상태이기 때문에 localhost에서만 작동합니다.

최종 완성되면 앱 검수를 받아야 실제 사용가능합니다.

 

HTML 마크업

bootstrap을 이용해서 마크업을 합니다.

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Welcome to Firebase Hosting</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/all.css">

    <style media="screen">
        #photoGalleryWrap {
            display: none
        }
        #joinScreen {
            display: none
        }
    </style>
</head>

<body>
    <div class="container">

        <!-- photoUpload -->
        <div id="photoGalleryWrap">
            <div class="content-area">
                
                <!-- Standard Form -->
                <h4>사진선택</h4>
                <form action="" method="post" enctype="multipart/form-data" id="uploadForm">
                    <div class="form-inline">
                        <div class="form-group">
                            <input type="file" name="files[]" id="uploadFiles">
                        </div>
                        <button type="submit" class="btn btn-sm btn-primary" id="btnUploadSubmit">Upload files</button>
                    </div>
                </form>

                <div class="row text-center text-lg-left" id="gallery">
                    <div class="col-lg-3 col-md-4 col-6">
                        <a href="#" class="d-block mb-4 h-100">
                            <img class="img-fluid img-thumbnail" src="https://source.unsplash.com/pWkk7iiCoDM/400x300"
                                alt="">
                        </a>
                    </div>
                    <div class="col-lg-3 col-md-4 col-6">
                        <a href="#" class="d-block mb-4 h-100">
                            <img class="img-fluid img-thumbnail" src="https://source.unsplash.com/aob0ukAYfuI/400x300"
                                alt="">
                        </a>
                    </div>
                    <div class="col-lg-3 col-md-4 col-6">
                        <a href="#" class="d-block mb-4 h-100">
                            <img class="img-fluid img-thumbnail" src="https://source.unsplash.com/EUfxH-pze7s/400x300"
                                alt="">
                        </a>
                    </div>
                    <div class="col-lg-3 col-md-4 col-6">
                        <a href="#" class="d-block mb-4 h-100">
                            <img class="img-fluid img-thumbnail" src="https://source.unsplash.com/M185_qYH8vg/400x300"
                                alt="">
                        </a>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-12">
                        <div class="form-group">
                            <button type="submit" class="btn btn-primary btn-block" id="btnLogout"> 로그아웃
                            </button>
                        </div> <!-- form-group// -->
                    </div>
                </div> <!-- .row// -->

            </div>
        </div>

        <div id="userLoginWrap">
            <div class="card">
                <article class="card-body" id="loginScreen">
                    <a href="#" class="float-right btn btn-outline-primary" id="btnToJoin">이메일 회원가입</a>
                    <h4 class="card-title mb-4 mt-1">로그인</h4>
                    <p>
                        <a href="" class="btn btn-block btn-outline-danger" id="btnGoogle">
                            <i class="fab fa-google"></i> 구글 로그인
                        </a>
                        <a href="" class="btn btn-block btn-outline-primary" id="btnFacebook">
                            <i class="fab fa-facebook-f"></i> 페이스북 로그인
                        </a>
                    </p>
                    <hr>
                    <form>
                        <div class="form-group">
                            <input name="" class="form-control" placeholder="Email" type="email" id="userLoginEmail">
                        </div> <!-- form-group// -->
                        <div class="form-group">
                            <input class="form-control" placeholder="******" type="password" id="userLoginPassword">
                        </div> <!-- form-group// -->
                        <div class="row">
                            <div class="col-md-6">
                                <div class="form-group">
                                    <button type="submit" class="btn btn-primary btn-block" id="btnLogin"> 로그인
                                    </button>
                                </div> <!-- form-group// -->
                            </div>
                            <div class="col-md-6 text-right">
                                <a class="small" href="#" id="btnFindPassword">비밀번호 찾기</a>
                            </div>
                        </div> <!-- .row// -->
                    </form>
                </article>

                <article class="card-body" id="joinScreen">
                    <a href="#" class="float-right btn btn-outline-primary" id="btnToLogin">로그인</a>
                    <h4 class="card-title mb-4 mt-1">회원가입</h4>
                    <hr>
                    <form>
                        <div class="form-group">
                            <input name="" class="form-control" placeholder="이름" type="text" id="userJoinName">
                        </div> <!-- form-group// -->
                        <div class="form-group">
                            <input name="" class="form-control" placeholder="Email" type="email" id="userJoinEmail">
                        </div> <!-- form-group// -->
                        <div class="form-group">
                            <input class="form-control" placeholder="******" type="password" id="userJoinPassword1">
                        </div> <!-- form-group// -->
                        <div class="form-group">
                            <input class="form-control" placeholder="******" type="password" id="userJoinPassword2">
                        </div> <!-- form-group// -->
                        <div class="row">
                            <div class="col-md-6">
                                <div class="form-group">
                                    <button type="submit" class="btn btn-primary btn-block" id="btnJoin"> 회원가입
                                    </button>
                                </div> <!-- form-group// -->
                            </div>
                        </div> <!-- .row// -->
                    </form>
                </article>
            </div>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <script defer src="/__/firebase/6.6.2/firebase-app.js"></script>
    <script defer src="/__/firebase/6.6.2/firebase-auth.js"></script>
    <script defer src="/__/firebase/6.6.2/firebase-database.js"></script>
    <script defer src="/__/firebase/6.6.2/firebase-messaging.js"></script>
    <script defer src="/__/firebase/6.6.2/firebase-storage.js"></script>
    <script defer src="/__/firebase/init.js"></script>
    <script defer src="index.js"></script>

</body>

</html>

로그인 화면

이메일 회원가입 화면

갤러리 화면

 

Google 로그인

javascript는 ES6의 Class로 제작되었기 때문에 크롬 등 최신 브라우저에서만 작동합니다.

babel을 사용해서 ES5로 변경하는것은 별도로 다루지 않습니다.

https://romeoh.tistory.com/search/babel

/**
 * PhotoApp class
 */
class PhotoApp {
    /**
     * 생성자
     */
    constructor() {
        this.init();
        this.initEvent();
    }

    /**
     * 변수할당
     */
    init() {

        // IndexedDB 정보
        this.INDEXED_DB_NAME = 'USER'
        this.INDEXED_VERSION = 1
        this.INDEXED_STORE = 'Users'
        
        this.auth = firebase.auth()
        this.$btnGoogle = $('#btnGoogle')
    }

    /**
     * Event Binding
     */
    initEvent() {
        this.$btnGoogle.on('click', this.onGoogleClick.bind(this))
    }

    /**
     * Google 버튼 눌러서 로그인 / 회원가입
     */
    onGoogleClick(e) {
        e.preventDefault()
        const googleProvider = new firebase.auth.GoogleAuthProvider()
        this.auth
            .setPersistence(firebase.auth.Auth.Persistence.SESSION)
            .then(this.signInWithPopup.bind(this, googleProvider))
            .catch(error => console.error('인증상태 설정 중 오류' , error))
    }

    /**
     * 구글 / 페이스북 로그인 팝업을 띄운다
     */
    signInWithPopup(provider) {
        this.auth
            .signInWithPopup(provider)
            .then(result => {
                // 로그인 결과값을 DB에 저장
                this.saveUserAtDB(result.user)
                    .then(user => {
                        // 로그인 성공 시 갤러리 입장
                        this.enterGallery()
                    })
            })
            .catch(error => console.error('로그인 오류', error))
    }

    /**
     * 회원가입 후 user 정보를 indexedDB에 저장한다.
     */
    saveUserAtDB(user) {
        if (!indexedDB) {
            return
        }
        return new Promise((resolve, reject) => {
            var request = indexedDB.open(this.INDEXED_DB_NAME, this.INDEXED_VERSION)
            const storeName = this.INDEXED_STORE
            request.onupgradeneeded = () => {
                const db = request.result
                const store = db.createObjectStore(storeName, {keyPath: 'uid'})
            }
            request.onsuccess = () => {
                const db = request.result
                const tx = db.transaction([storeName], 'readwrite')
                const store = tx.objectStore(storeName)
                
                // 저장된 user.uid를 불러온다.
                store.get(user.uid).onsuccess = event => {
                    const data = event.target.result
                    console.log('query: ', data)
                    if (!data) {
                        // 저장된 uid가 없으면 저장한다.
                        const newUser = {
                            uid: user.uid
                            , email: user.email
                            , photoURL: user.photoURL ? user.photoURL : ''
                            , displayName: user.displayName
                        }
                        store.put(newUser)
                        resolve(newUser)
                    }
                    resolve(data)
                }
            }
            request.oncomplete = () => {
                console.log('트랜잭션 완료')
                db.close();
            }
            request.onerror = error => {
                reject(error)
            }
        })
    }

    /**
     * 갤러리 입장
     * 로그인/회원가입/로그인 세션상태 체크 후 갤러리 화면으로 전환함
     */
    enterGallery() {
        this.$memberWrap.hide()
        this.$galleryWrap.show()
    }
}

/**
 * DOM Content Loaded
 */
document.addEventListener('DOMContentLoaded', () => {
    window.photoApp = new PhotoApp();
});

위 코드는 Google 버튼을 눌렀을때 Google 팝업창에서 인증후 User 정보를 indexedDB에 저장하는 로직입니다.

 

로그인후 Application > IndexedDB > Users에 User정보가 저장됩니다.

Firebase > Authentication > 사용자에 Google 사용자가 저장됩니다.

 

Facebook 로그인

/**
 * PhotoApp class
 */
class PhotoApp {
    /**
     * 생성자
     */
    constructor() {
        this.init();
        this.initEvent();
    }

    /**
     * 변수할당
     */
    init() {

        // IndexedDB 정보
        this.INDEXED_DB_NAME = 'USER'
        this.INDEXED_VERSION = 1
        this.INDEXED_STORE = 'Users'
        
        this.auth = firebase.auth()
        this.$btnFacebook = $('#btnFacebook')
    }

    /**
     * Event Binding
     */
    initEvent() {
        this.$btnFacebook.on('click', this.onFacebookClick.bind(this))
    }

    /**
     * Facebook 버튼 눌러서 로그인 / 회원가입
     */
    onFacebookClick(e) {
        e.preventDefault();
        const facebookProvider = new firebase.auth.FacebookAuthProvider()
        this.auth
            .setPersistence(firebase.auth.Auth.Persistence.SESSION)
            .then(this.signInWithPopup.bind(this, facebookProvider))
            .catch(error => console.error('인증상태 설정 중 오류' , error))
    }

    /**
     * 구글 / 페이스북 로그인 팝업을 띄운다
     */
    signInWithPopup(provider) {
        this.auth
            .signInWithPopup(provider)
            .then(result => {
                // 로그인 결과값을 DB에 저장
                this.saveUserAtDB(result.user)
                    .then(user => {
                        // 로그인 성공 시 갤러리 입장
                        this.enterGallery()
                    })
            })
            .catch(error => console.error('로그인 오류', error))
    }

    /**
     * 갤러리 입장
     * 로그인/회원가입/로그인 세션상태 체크 후 갤러리 화면으로 전환함
     */
    enterGallery() {
        this.$memberWrap.hide()
        this.$galleryWrap.show()
    }
}

/**
 * DOM Content Loaded
 */
document.addEventListener('DOMContentLoaded', () => {
    window.photoApp = new PhotoApp();
});

Facebook 로그인 로직입니다.

Facebook 사용자도 등록되었습니다.

 

이메일 등록 및 이메일 로그인

/**
 * PhotoApp class
 */
class PhotoApp {
    /**
     * 생성자
     */
    constructor() {
        this.init();
        this.initEvent();
    }

    /**
     * 변수할당
     */
    init() {

        // IndexedDB 정보
        this.INDEXED_DB_NAME = 'USER'
        this.INDEXED_VERSION = 1
        this.INDEXED_STORE = 'Users'
        
        this.auth = firebase.auth()
        this.$btnGoogle = $('#btnGoogle')
        this.$btnFacebook = $('#btnFacebook')
        this.$btnToJoin = $('#btnToJoin')
        this.$btnToLogin = $('#btnToLogin')
        this.$btnJoin = $('#btnJoin')
        this.$btnLogin = $('#btnLogin')
        this.$btnLogout = $('#btnLogout')
        
        this.$loginScreen = $('#loginScreen')
        this.$joinScreen = $('#joinScreen')
        this.$memberWrap = $('#userLoginWrap')
        this.$galleryWrap = $('#photoGalleryWrap')
    }

    /**
     * Event Binding
     */
    initEvent() {
        this.$btnGoogle.on('click', this.onGoogleClick.bind(this))
        this.$btnFacebook.on('click', this.onFacebookClick.bind(this))
        this.$btnToJoin.on('click', this.onToJoinClick.bind(this))
        this.$btnToLogin.on('click', this.onToLoginClick.bind(this))
        this.$btnJoin.on('click', this.createEmailUser.bind(this))
        this.$btnLogin.on('click', this.onEmailLoginClick.bind(this))
        this.$btnLogout.on('click', this.onLogoutClick.bind(this))

        // 로그인 세션을 체크함
        this.auth.onAuthStateChanged(this.onAuthChange.bind(this));
    }

    /**
     * 인증정보가 변화되면 처리함
     */
    onAuthChange(user) {
        if (user) {
            console.log('user login: ', user)
            this.enterGallery()
        } else {
            console.log('user logout')
            //this.setLogout()
        }
    }

    /**
     * Google 버튼 눌러서 로그인 / 회원가입
     */
    onGoogleClick(e) {
        e.preventDefault()
        const googleProvider = new firebase.auth.GoogleAuthProvider()
        this.auth
            .setPersistence(firebase.auth.Auth.Persistence.SESSION)
            .then(this.signInWithPopup.bind(this, googleProvider))
            .catch(error => console.error('인증상태 설정 중 오류' , error))
    }

    /**
     * Facebook 버튼 눌러서 로그인 / 회원가입
     */
    onFacebookClick(e) {
        e.preventDefault();
        const facebookProvider = new firebase.auth.FacebookAuthProvider()
        this.auth
            .setPersistence(firebase.auth.Auth.Persistence.SESSION)
            .then(this.signInWithPopup.bind(this, facebookProvider))
            .catch(error => console.error('인증상태 설정 중 오류' , error))
    }

    /**
     * 구글 / 페이스북 로그인 팝업을 띄운다
     */
    signInWithPopup(provider) {
        this.auth
            .signInWithPopup(provider)
            .then(result => {
                // 로그인 결과값을 DB에 저장
                this.saveUserAtDB(result.user)
                    .then(user => {
                        // 로그인 성공 시 갤러리 입장
                        this.enterGallery()
                    })
            })
            .catch(error => console.error('로그인 오류', error))
    }

    /**
     * 이메일로 회원 가입
     */
    createEmailUser() {
        const userName = $.trim($('#userJoinName').val())
        const userEmail = $.trim($('#userJoinEmail').val())
        const password = $.trim($('#userJoinPassword1').val())
        const repassword = $.trim($('#userJoinPassword2').val())

        if (this.validationJoinForm(userName, userEmail, password, repassword)) {
            
            // 회원가입 성공시 DB에 user 정보를 넣음
            const cbCreateUserWithEmail = result => {
                this.saveUserAtDB(result.user, userName)
                    .then(user => {
                        // 이메일 회원가입 성공후 갤러리 입장
                        this.enterGallery()
                    })
                console.log('이메일 가입 성공: ', result.user)
            }

            // 이메일로 회원가입 요청
            const cbAfterPersistence = () => {
                return this.auth.createUserWithEmailAndPassword(userEmail, password)
                    .then(cbCreateUserWithEmail.bind(this))
                    .catch(error => {
                        console.error('이메일 가입 에러: ', error)
                        switch (error.code) {
                            case 'auth/email-already-in-use':
                                alert('이미 사용중인 이메일 입니다.')
                                break;
                            case 'auth/invalid-email':
                                alert('유효하지 않은 메일입니다')
                                break;
                            case 'auth/operation-not-allowed':
                                alert('이메일 가입이 중지되었습니다.')
                                break;
                            case 'auth/weak-password':
                                alert('비밀번호를 6자리 이상 필요합니다')
                                break;
                        }
                    })
            }

            this.auth.setPersistence(firebase.auth.Auth.Persistence.SESSION)
                .then(cbAfterPersistence.bind(this))
                .catch(error => console.error('인증 상태 설정 중 에러 발생' , error))
        }
    }

    /**
     * 이메일로 로그인
     */
    onEmailLoginClick() {
        const userEmail = $.trim($('#userLoginEmail').val())
        const password = $.trim($('#userLoginPassword').val())
        
        if (this.validationLoginForm(userEmail, password)) {

            // 이메일로 로그인 요청
            const cbLoginInEmail = () => {
                return this.auth.signInWithEmailAndPassword(userEmail, password)
                    .then(result => {
                        // 로그인 성공후 갤러리 입장
                        this.enterGallery()
                    })
                    .catch(error => {
                        console.error('이메일 로그인 에러', error);
                        switch(error.code){
                            case 'auth/invalid-email':
                                alert('유효하지 않은 메일입니다');
                                break;
                            case 'auth/user-disabled':
                                alert('사용이 정지된 유저 입니다.')
                                break;
                            case 'auth/user-not-found':
                                alert('사용자를 찾을 수 없습니다.')
                                break;
                            case 'auth/wrong-password':
                                alert("잘못된 패스워드 입니다.");
                                break;
                        }
                    })
            }

            this.auth.setPersistence(firebase.auth.Auth.Persistence.SESSION)
                .then(cbLoginInEmail.bind(this))
        }
    }

    /**
     * 회원가입 후 user 정보를 indexedDB에 저장한다.
     */
    saveUserAtDB(user) {
        if (!indexedDB) {
            return
        }
        return new Promise((resolve, reject) => {
            var request = indexedDB.open(this.INDEXED_DB_NAME, this.INDEXED_VERSION)
            const storeName = this.INDEXED_STORE
            request.onupgradeneeded = () => {
                const db = request.result
                const store = db.createObjectStore(storeName, {keyPath: 'uid'})
            }
            request.onsuccess = () => {
                const db = request.result
                const tx = db.transaction([storeName], 'readwrite')
                const store = tx.objectStore(storeName)
                
                // 저장된 user.uid를 불러온다.
                store.get(user.uid).onsuccess = event => {
                    const data = event.target.result
                    console.log('query: ', data)
                    if (!data) {
                        // 저장된 uid가 없으면 저장한다.
                        const newUser = {
                            uid: user.uid
                            , email: user.email
                            , photoURL: user.photoURL ? user.photoURL : ''
                            , displayName: user.displayName
                        }
                        store.put(newUser)
                        resolve(newUser)
                    }
                    resolve(data)
                }
            }
            request.oncomplete = () => {
                console.log('트랜잭션 완료')
                db.close();
            }
            request.onerror = error => {
                reject(error)
            }
        })
    }

    /**
     * 로그아웃 버튼 클릭
     * 로그아웃 하고 로그인 화면으로 전환함
     */
    onLogoutClick() {
        this.auth.signOut()
        this.goToLogin()
    }

    /**
     * 갤러리 입장
     * 로그인/회원가입/로그인 세션상태 체크 후 갤러리 화면으로 전환함
     */
    enterGallery() {
        this.$memberWrap.hide()
        this.$galleryWrap.show()
    }

    /**
     * 로그인 페이지로 전환
     * 로그아웃 처리후 로그인 화면으로 전환함
     */
    goToLogin() {
        this.$memberWrap.show()
        this.$galleryWrap.hide()
    }

    /**
     * 이메일 회원가입 화면으로 전환
     */
    onToJoinClick(e) {
        e.preventDefault()
        this.$loginScreen.hide()
        this.$joinScreen.show()
    }
    
    /**
     * 로그인 화면으로 전환
     */
    onToLoginClick(e) {
        e.preventDefault()
        this.$loginScreen.show()
        this.$joinScreen.hide()
    }

    /**
     * email 로그인 폼 유효성 검사
     */
    validationLoginForm(userEmail, password) {
        if (!this.emailCheck(userEmail)) {
            alert('이메일 형식에 맞지 않습니다.')
            return false
        }
        if (!password) {
            alert('패스워드를 입력하세요.')
            return false
        }
        return true
    }

    /**
     * email 가입 폼 유효성 검사
     */
    validationJoinForm(userName, userEmail, password, repassword) {
        if (!userName) {
            alert('이름은 필수입니다.')
            return false
        }
        if (!this.emailCheck(userEmail)) {
            alert('이메일 형식에 맞지 않습니다.')
            return false
        }
        if (!password || !repassword) {
            alert('패스워드를 입력하세요.')
            return false
        }
        if (password != repassword) {
            alert('패스워드가 동일하지 않습니다.')
            return false
        }
        return true
    }

    /**
     * 이메일 형식 체크
     */
    emailCheck(email) {
        if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
            return true;
        }
        return false;
    }
}

/**
 * DOM Content Loaded
 */
document.addEventListener('DOMContentLoaded', () => {
    window.photoApp = new PhotoApp();
});

이메일 회원가입, 이메일 로그인 처리된 최종 코드입니다.

 

크롬 Console에서 회원가입시 로그를 확인 할 수 있습니다.

Email 회원도 등록되었습니다.

로그아웃하고 다시 로그인 해봅니다.

 

 

Firebase 목록

반응형
Comments