저번 시간에서 이메일로 계정을 생성하고 이메일과 비밀번호로 로그인을 하고, 로그아웃을 하는 과정까지 구현했다. 유저의 가입과 로그인, 로그아웃까지의 첫 스타트를 끊었지만 해당 방식에는 많은 구멍이 있다는 것을 알 수 있다.
이와 같은 문제로 인해, 이메일로 가입을 하고 나서 실제 존재하는 이메일인지 확인하여 인증 요청을 보내고, 인증이 되었을 때만 로그인할 수 있도록 해 보자.
우선은 이메일이 제대로 전송되는지와 인증이 잘 되는지에 대한 부분만 먼저 테스트를 진행해보자.
로그인을 하는 과정에 로그인에 성공했을 때 해당 이메일이 인증이 되어 있을 경우 로비로 넘어가고, 아니면 인증 메일을 보내는 방식을 취해보자.
private void Login()
{
...(로그인 실패시)
Debug.Log("로그인 성공");
FirebaseUser user = task.Result.User;
if(user.IsEmailVerified == true)
{
lobbyPanel.SetActive(true);
gameObject.SetActive(false);
}
else
{
user.SendEmailVerificationAsync().ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("인증 이메일 전송이 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"인증 이메일 전송이 실패. 이유 : {task.Exception}");
return;
}
Debug.Log("인증 이메일 전송 성공");
});
}
});
}
이와 같이 이메일 주소 인증은 어떤 식으로 이루어지는지 확인해보자.
파이어베이스를 확인해보면 이와 같이 이메일 주소 인증과 관련한 템플릿을 제공하고 있다. 그대로 사용해도 무방하지만 상황에 따라서는 전송되는 내용을 원하는대로 수정할 수도 있다.
그러면 실제로 적용이 되는지 확인해보자.
이 부분은 우선 감안하고, 메일이 왔는지 확인해보자.
아무래도 수신자나 메일 내용 때문인지 스팸함에 들어가 있는 것을 확인할 수 있었다. 여기에서 해당 링크를 누르면 계정 인증이 가능하다.
이와 같이 계정 인증을 완료하면 접속할 수 있게 된다.
위의 이메일 인증과정에서는 이메일의 인증 과정이 잘 진행되고 있는지 표시하는 UI적 요소가 없어 다소 불편하게 느껴졌을 수도 있다. 이걸 보완하기 위해서 UI적 요소를 추가하고, 인증하면 다음 로비 단계로 넘어갈 수 있도록 처리하려고 한다.
우선은 이와 같이 단순한 대기 패널을 만들어보자.
이메일을 인증하던 부분을 이곳 패널에서 처리하도록 로그인 패널을 수정하고 EmailPanel 스크립트를 만들어보자.
private void Login()
{
FirebaseManager.Auth.SignInWithEmailAndPasswordAsync(idInput.text, passInput.text)
.ContinueWithOnMainThread(task =>
{
if(task.IsCanceled)
{
Debug.LogError("로그인이 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"로그인이 실패함. 이유 : {task.Exception}");
ShowPopUp("Login Failed");
return;
}
Debug.Log("로그인 성공");
FirebaseUser user = task.Result.User;
if (user.IsEmailVerified == true)
{
lobbyPanel.SetActive(true);
gameObject.SetActive(false);
}
else
{
// 이메일 패널을 활성화
emailPanel.SetActive(true);
gameObject.SetActive(false);
}
});
}
using Firebase.Auth;
using Firebase.Extensions;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class EmailPanel : MonoBehaviour
{
[SerializeField] GameObject loginPanel;
[SerializeField] GameObject nicknamePanel;
[SerializeField] Button backButton;
private void Awake()
{
backButton.onClick.AddListener(Back);
}
private void OnEnable()
{
FirebaseManager.Auth.CurrentUser.SendEmailVerificationAsync()
.ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("인증 이메일 전송이 취소됨");
return;
}
if (task.IsFaulted)
{
Debug.LogError($"인증 이메일 전송이 실패. 이유 : {task.Exception}");
return;
}
Debug.Log("인증 이메일 전송 성공");
emailVerificationRoutine = StartCoroutine(EmailVerificationRoutine());
});
}
private void Back()
{
FirebaseManager.Auth.SignOut();
loginPanel.SetActive(true);
gameObject.SetActive(false);
}
Coroutine emailVerificationRoutine;
IEnumerator EmailVerificationRoutine()
{
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
WaitForSeconds delay = new WaitForSeconds(2f);
while(true)
{
yield return delay;
user.ReloadAsync();
if(user.IsEmailVerified)
{
Debug.Log("인증완료");
nicknamePanel.SetActive(true);
gameObject.SetActive(false);
StopCoroutine(emailVerificationRoutine);
}
else
{
Debug.Log("인증대기중");
}
}
}
}
방식은 코루틴으로 2초마다 인증이 되었는지 확인하면서, 인증이 완료된 것이 확인되면 다음 화면으로 넘어가는 방식이다.
이와 같은 방식으로 이메일 인증 절차와 동시에 화면이 넘어가는 부분까지 구현했다.
LoginPanel에서 비밀번호를 재설정할 수 있는 메일을 보내는 함수를 추가해보자.
private void ResetPass()
{
FirebaseManager.Auth.SendPasswordResetEmailAsync(idInput.text)
.ContinueWithOnMainThread(task =>
{
if(task.IsCanceled)
{
Debug.LogError("패스워드 재설정 이메일 전송 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"패스워드 재설정 이메일 전송 실패. 이유 : {task.Exception}");
ShowPopUp("Invalid Email.");
return;
}
ShowPopUp("Password reset Email Sended.");
});
}
해당 기능의 경우 UI상으로 팝업을 띄울 수 있도록 구현해보았다. 이를 통해서 비밀번호 재설정 기능을 실행시켜보자.
해당 계정이 가입된 계정이면 비밀번호를 변경할 수 있으며, 메일이 전송되었다는 팝업이 떴다.
이제 메일이 제대로 왔는지 확인해보자.
이와 같이 마찬가지로 메일이 온 것을 확인할 수 있다. 해당 링크를 클릭하면 비밀번호 변경이 가능하다.
비밀번호가 정상적으로 변경되고, 변경된 비밀번호로 접속할 수 있는 것을 확인할 수 있다.
지금까지의 과정에서 닉네임을 설정하는 과정이 없었다. 닉네임을 설정하는 과정을 추가하고, 더 나아가 이메일 인증 절차를 거쳤을 때 바로 닉네임 설정하기로 넘어가도록 UI 설정을 변경해보도록 한다.
닉네임 설정 패널은 간단하게 수정했다. 해당 패널에 해당하는 컴포넌트를 만들고, 연결 과정을 넣었다.
using Firebase.Auth;
using Firebase.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class NicknamePanel : MonoBehaviour
{
[Header("팝업")]
[SerializeField] GameObject loginPanel;
[SerializeField] GameObject lobbyPanel;
[Header("입력창")]
[SerializeField] TMP_InputField nicknameInput;
[Header("버튼")]
[SerializeField] Button confirmButton;
[SerializeField] Button backButton;
[Header("팝업")]
[SerializeField] GameObject popUpPanel;
[SerializeField] TMP_Text popupText;
[SerializeField] Button popupCloseButton;
private void Awake()
{
confirmButton.onClick.AddListener(Confirm);
backButton.onClick.AddListener(Back);
popupCloseButton.onClick.AddListener(Close);
}
private void Confirm()
{
if (nicknameInput.text == "")
{
ShowPopUp("Invalid Nickname.");
return;
}
UserProfile profile = new UserProfile();
profile.DisplayName = nicknameInput.text;
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
user.UpdateUserProfileAsync(profile)
.ContinueWithOnMainThread(task =>
{
if(task.IsCanceled)
{
Debug.LogError("유저 닉네임 설정 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"유저 닉네임 설정 실패. 이유 : {task.Exception}");
ShowPopUp("Failed to set Nickname.");
return;
}
Debug.Log("유저 닉네임 설정 성공");
lobbyPanel.SetActive(true);
gameObject.SetActive(false);
});
}
private void Back()
{
FirebaseManager.Auth.SignOut();
loginPanel.SetActive(true);
gameObject.SetActive(false);
}
private void ShowPopUp(string description)
{
popUpPanel.SetActive(true);
popupText.text = description;
}
private void Close()
{
popUpPanel.SetActive(false);
}
}
이와 같이 추가하고, Login 패널과 Email 패널에서 계정을 생성 / 혹은 생성한 계정의 닉네임이 없을 경우 해당 화면으로 넘어가도록 설정하였다.
private void Login()
{
FirebaseManager.Auth.SignInWithEmailAndPasswordAsync(idInput.text, passInput.text)
.ContinueWithOnMainThread(task =>
{
if(task.IsCanceled)
{
Debug.LogError("로그인이 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"로그인이 실패함. 이유 : {task.Exception}");
ShowPopUp("Login Failed");
return;
}
Debug.Log("로그인 성공");
FirebaseUser user = task.Result.User;
if (user.IsEmailVerified == true)
{
// 닉네임이 있을 경우 로비로 진행
// 없을 경우 닉네임 설정화면으로 이동
if (user.DisplayName == "")
{
nicknamePanel.SetActive(true);
gameObject.SetActive(false);
}
else
{
lobbyPanel.SetActive(true);
gameObject.SetActive(false);
}
}
else
{
emailPanel.SetActive(true);
gameObject.SetActive(false);
}
});
}
처음 등록한 이메일의 인증을 완료하여 로그인했을 시에 닉네임이 없을 것이므로 바로 닉네임 설정 부분으로 넘어가도록 한다.
IEnumerator EmailVerificationRoutine()
{
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
WaitForSeconds delay = new WaitForSeconds(2f);
while(true)
{
yield return delay;
user.ReloadAsync();
if(user.IsEmailVerified)
{
Debug.Log("인증완료");
nicknamePanel.SetActive(true);
gameObject.SetActive(false);
StopCoroutine(emailVerificationRoutine);
}
else
{
Debug.Log("인증대기중");
}
}
}
이와 같이 설정하여 로비로 넘어갔을 경우, 닉네임이 제대로 반영된 것을 확인할 수 있다.
변경할 정보는 닉네임과 비밀번호로 하려고 한다.
원래는 이메일 정보도 같이 변경하는 방식을 구현하려고 했으나, 이와 같은 문제점이 있었다.
UpdateEmailAsync는 이제 사용되지 않는 함수이며, 해당 방식이 사용되지 않는 이유는 이메일이란 정보의 특수성 때문이다.
파이어베이스의 경우 이메일을 인증하는 단계가 있기 때문에, 이메일을 단순하게 변경이 가능할 경우 해당 이메일에 대한 인증 절차를 다시 거쳐야 하는 문제점이 발생한다. 따라서, 이메일을 인증절차에 따라 다시 변경하는 방식만을 지원하는 것으로 바뀌게 되었다.
따라서 업데이트 이전에 다시 인증 메일을 보내 업데이트 하는 식으로 구현할 수 있으며, 이는 필요할 시에 구현하도록 한다.
EditPanel은 다음과 같이 간단하게 디자인했다.
스크립트는 아래와 같이 팝업으로 표시하는 내용까지 반영하여 작성했다.
using Firebase.Auth;
using Firebase.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class EditPanel : MonoBehaviour
{
[SerializeField] GameObject lobbyPanel;
[SerializeField] TMP_InputField nameInput;
[SerializeField] TMP_InputField passInput;
[SerializeField] TMP_InputField passConfirmInput;
[SerializeField] TMP_Text emailText;
[SerializeField] TMP_Text userIdText;
[SerializeField] Button NicknameConfirmButton;
[SerializeField] Button PasswordConfirmButton;
[SerializeField] Button backButton;
[Header("팝업")]
[SerializeField] GameObject popUpPanel;
[SerializeField] TMP_Text popupText;
[SerializeField] Button popupCloseButton;
private void Awake()
{
NicknameConfirmButton.onClick.AddListener(ChangeNickname);
PasswordConfirmButton.onClick.AddListener(ChangePassword);
backButton.onClick.AddListener(Back);
popupCloseButton.onClick.AddListener(Close);
}
private void OnEnable()
{
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
emailText.text = user.Email;
userIdText.text = user.UserId;
nameInput.text = user.DisplayName;
}
private void ChangeNickname()
{
if(nameInput.text == "")
{
ShowPopUp("Invalid Nickname");
return;
}
UserProfile profile = new UserProfile();
profile.DisplayName = nameInput.text;
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
user.UpdateUserProfileAsync(profile)
.ContinueWithOnMainThread(task =>
{
if(task.IsCanceled)
{
Debug.LogError("닉네임 변경 취소됨");
return;
}
if(task.IsFaulted)
{
Debug.LogError($"닉네임 변경 실패. 이유 {task.Exception}");
ShowPopUp("Nickname Change Failed");
return;
}
ShowPopUp("Nickname Changed");
Debug.Log("닉네임 변경 성공");
});
}
private void ChangePassword()
{
if(passInput.text != passConfirmInput.text)
{
Debug.LogError("비밀번호가 일치하지 않음");
return;
}
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
user.UpdatePasswordAsync(passInput.text)
.ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("비밀번호 변경 취소됨");
return;
}
if (task.IsFaulted)
{
Debug.LogError($"비밀번호 변경 실패. 이유 {task.Exception}");
ShowPopUp("Password Changed Failed.\nPlease input valid password.");
return;
}
ShowPopUp("Password Changed");
Debug.Log("비밀번호 변경 성공");
});
}
private void Back()
{
lobbyPanel.SetActive(true);
gameObject.SetActive(false);
}
private void ShowPopUp(string description)
{
popUpPanel.SetActive(true);
popupText.text = description;
}
private void Close()
{
popUpPanel.SetActive(false);
}
}
이와 같이 반영한 내용이 잘 적용되는지 확인해보자.
닉네임의 변경 및 비밀번호의 변경이 잘 적용되는 것을 확인했다.
계정 탈퇴 기능을 만들기 위해 로비에 추가로 버튼을 넣었다.
이에 따라 로비 기능에 해당 기능을 추가했다.
using Firebase.Auth;
using Firebase.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class LobbyPanel : MonoBehaviour
{
[Header("패널")]
[SerializeField] GameObject loginPanel;
[SerializeField] GameObject editPanel;
[Header("텍스트")]
[SerializeField] TMP_Text emailText;
[SerializeField] TMP_Text nameText;
[SerializeField] TMP_Text userIdText;
[Header("버튼")]
[SerializeField] Button logoutButton;
[SerializeField] Button editProfileButton;
[SerializeField] Button deleteUserButton;
[Header("승인팝업")]
[SerializeField] GameObject popupConfirmPanel;
[SerializeField] TMP_Text popupConfirmText;
[SerializeField] Button popupConfirmButton;
[SerializeField] Button popupRevertButton;
private void Awake()
{
logoutButton.onClick.AddListener(Logout);
editProfileButton.onClick.AddListener(EditProfile);
deleteUserButton.onClick.AddListener(ShowConfirmPopUp);
popupConfirmButton.onClick.AddListener(ConfirmSignUp);
popupRevertButton.onClick.AddListener(RevertSignUp);
}
private void OnEnable()
{
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
emailText.text = user.Email;
nameText.text = user.DisplayName;
userIdText.text = user.UserId;
}
private void Logout()
{
FirebaseManager.Auth.SignOut();
loginPanel.SetActive(true);
gameObject.SetActive(false);
}
private void EditProfile()
{
editPanel.SetActive(true);
gameObject.SetActive(false);
}
private void ShowConfirmPopUp()
{
popupConfirmPanel.SetActive(true);
popupConfirmText.text = "Permanently delete account.\nContinue?";
}
private void ConfirmSignUp()
{
DeleteUser();
popupConfirmPanel.SetActive(false);
}
private void RevertSignUp()
{
popupConfirmPanel.SetActive(false);
}
private void DeleteUser()
{
FirebaseUser user = FirebaseManager.Auth.CurrentUser;
user.DeleteAsync()
.ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("유저 삭제 취소됨");
return;
}
if (task.IsFaulted)
{
Debug.LogError($"유저 삭제 실패, 이유 : {task.Exception}");
return;
}
Debug.Log("유저 삭제 성공");
FirebaseManager.Auth.SignOut();
loginPanel.SetActive(true);
gameObject.SetActive(false);
});
}
}
이와 같이 했을 때 해당 기능이 잘 작동되는지 확인해보자.
이와 같이 팝업이 떴을 때 선택이 가능하며, 계정을 지운다고 선택했을 때 영구적으로 계정을 지울 수 있다.
계정이 정상적으로 삭제된 것을 확인할 수 있었다.
익명 계정은 흔히 생각하면 게스트 로그인 기능을 말한다. 사용자는 별도의 이메일이나 비밀번호 설정 없이 계정을 생성할 수 있고 생성하는 방법은 다음과 같다.
auth.SignInAnonymouslyAsync()
.ContinueWithOnMainThread(task => {});
익명 계정은 30일간 유지되는 계정이며, 영구 계정으로 전환하지 않으면 자동으로 삭제된다.
영구 계정으로 전환하는 방법은 Firebase.Auth.FirebaseAuth.SignInAndRetrieveWithCredentialAsync 메서드 호출을 통해서 가능하다.
지금까지의 이메일 설정 및 인증 과정 외의 구글 로그인, 플레이스토어 로그인, 깃허브, 트위터, 마이크로 소프트 등의 로그인에 대한 것은 메뉴얼을 통해 확인할 수 있다.
특징적인 것으로는 플레이스토어 로그인의 경우는 모바일 게임에서만 사용 가능하다는 점이 있다.
간단하게 구글 로그인 방식에 대해 알아보자면 다음과 같이 진행된다.
프로젝트의 공개용 이름을 설정하고 우선 저장해보자.
그러면 이와 같이 SHA 디지털 지문을 요구한다는 멘트가 떠오르는데, 이 SHA 디지털 지문은 아래와 같이 설정한다.
프로젝트 설정 > 일반에 들어가서 확인해보면 이와 같이 디지털 지문 추가 부분을 확인할 수 있다.
이 디지털 지문의 경우에는 Google Play Console에서 발급할 수 있다.
해당 사이트는 구글 플레이스토어에서 앱을 출시하기 위한 개발자 계정을 생성하고 관리할 수 있는 곳이다.
이런 개발자 계정의 경우에는 신원 증명 및 25달러의 금액을 지불하여 내고 생성할 수 있으며, 해당 출입증을 가지고 생성할 수 있는 경우이다.