We wcześniejszym wpisie Czym jest FIrebase? przedstawiłem możliwości, jakie daje platforma Firebase. Jedną z funkcji jest autoryzacja użytkownika. Wykorzystywana jest do przechowywania danych dostępnych tylko dla danego użytkownika. Może się ona odbywać za pomocą adresu e-mail, portali społecznościowych – takich jak Facebook, Twitter, a także innych kont, np. Google, GitHub.
Celem tego wpisu będzie utworzenie prostej aplikacji internetowej, umożliwiającej założenie nowego konta, zalogowanie na już istniejące oraz wylogowanie. Aplikacja będzie wykorzystywała Bootstrapa dla uproszczenia budowy wyglądu. Poniżej znajdują się zrzuty ekranu przedstawiające wszystkie widoki, jakie posiadać będzie tworzona w tym wpisie aplikacja.
Utworzenie projektu w Firebase
Abyśmy mogli zacząć pracę z kodem, musimy na początku utworzyć projekt Firebase. Przechodzimy do głównej strony Firebase.com i klikamy Get started, a następnie wybieramy opcję Dodaj projekt. Pierwszym krokiem jest podanie nazwy projektu. W kolejnym jesteśmy pytani, czy chcemy skonfigurować usługę Google Analytics. Na potrzeby tego wpisu możemy wybrać opcję Nie teraz i kliknąć przycisk Utwórz projekt. Powinniśmy zobaczyć widok podobny do tego zaprezentowanego poniżej.
Teraz potrzebujemy jeszcze włączyć możliwość logowania za pomocą adresu e-mail. W tym celu przechodzimy do zakładki Authentication, a następnie przechodzimy do zakładki Metoda logowania. Wybieramy opcję E-mail/hasło i włączamy ją.
Tworzenie wyglądu aplikacji
Mamy już utworzony projekt Firebase, teraz możemy przystąpić do budowania aplikacji. Na początek utworzymy strukturę plików. Do projektu dodajemy index.html, app.js, style.css. Teraz możemy uzupełnić standardową strukturą index.html zgodną z HTML5.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Autoryzacja w Firebase</title> </head> <body> </body> </html>
Jak wcześniej wspomniałem, tworzona aplikacja będzie korzystała z Bootstrapa, aby ułatwić tworzenie wyglądu. Jednak abyśmy mogli korzystać z Bootstrapowych elementów, musimy wcześniej wczytać style do pliku HTML.
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <link rel="stylesheet" href="style.css">
Pozostaje nam dodać pliki JSowe.
<script src="https://www.gstatic.com/firebasejs/6.3.3/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/6.3.3/firebase-auth.js"></script> <script src="main.js"></script>
Jak można zobaczyć powyżej, do poprawnego działania autoryzacji, Firebase potrzebuje dwóch plików: podstawowego – firebase-app oraz modułu autoryzacyjnego – firebase-auth.
Możemy teraz uzupełnić element body o szkielet pisanej aplikacji.
<div class="container mt-5"> <div class="row"> <div class="col-6 offset-3 shadow rounded panel"> <div id="authHeader" class="row align-items-center text-center header"> <div class="col-6"><span id="loginHeaderBtn" class="active">Zaloguj</span></div> <div class="col-6"><span id="registerHeaderBtn">Zarejestruj</span></div> </div> <div id="dashboardHeader" class="row align-items-center text-center header"> <div class="col">Dashboard</div> </div> <div id="content"> <div id="messageAlert" class="alert alert-danger" role="alert"></div> </div> </div> </div> </div>
Kod ten odpowiada za utworzenie na środku strony panelu, w którym wyświetlane będą widoki. W przypadku, kiedy użytkownik będzie niezalogowany, nagłówek będzie składał się z dwóch klikanych elementów: Zaloguj oraz Zarejestruj. Klikając na określony nagłówek będzie on zmieniał widok na wybrany. Po zalogowaniu w nagłówku wyświetli się napis Dashboard. W elemencie zawierającym identyfikator content znajdować się będą wszystkie widoki. Jak możemy zobaczyć na powyższym kodzie, znajduję się tam również komunikat o błędzie.
Aby poprawnie wszystko się wyświetlało, należy dodać jeszcze kod css.
/* MAIN WINDOW */ .panel { padding: 0; margin-bottom: 70px; } /* HEADER */ .header { height: 50px; width: 100%; margin: 0; padding: 0; font-size: 24px; border-bottom: solid 1px #222; } .header > div > span { cursor: pointer; } .header .active { font-weight: bold; } /* CONTENT */ #content { padding: 20px; } #content button { width: 100%; } #messageAlert { display: none; }
Możemy przystąpić do utworzenia trzech widoków: logowania, rejestracji i dashboardu. Zaczniemy od widoku logowania.
<div id="loginVIew”> <form> <div class="form-group"> <label for="loginMailInput">Adres E-mail</label> <input type="email" class="form-control" id="loginMailInput" aria-describedby="emailHelp" placeholder="Podaj adres E-mail"> </div> <div class="form-group"> <label for="loginPasswordInput">Hasło</label> <input type="password" class="form-control" id="loginPasswordInput" placeholder="Podaj hasło"> </div> <button type="submit" id="loginBtn" class="btn btn-primary mb-3 animation">Zaloguj</button> </form> </div>
Do zalogowania użytkownika potrzebujemy dwóch pól tekstowych oraz przycisku potwierdzającego logowanie. Każde pole posiada identyfikator, dzięki któremu będziemy mogli odczytać wartość wprowadzoną przez użytkownika. Przycisk również posiada identyfikator, dzięki któremu będziemy nasłuchiwać akcji kliknięcia w przycisk przez użytkownika.
Kolejnym widokiem jakim się zajmiemy będzie rejestracja nowych użytkowników.
<div id="registerView"> <form> <div class="form-group"> <label for="registerMailInput">Adres E-mail</label> <input type="email" class="form-control" id="registerMailInput" aria-describedby="emailHelp" placeholder="Podaj adres E-mail"> </div> <div class="form-group"> <label for="registerPasswordInput">Hasło</label> <input type="password" class="form-control" id="registerPasswordInput" placeholder="Podaj hasło"> </div> <div class="form-group"> <label for="registerRepeatPasswordInput">Powtórz hasło</label> <input type="password" class="form-control" id="registerRepeatPasswordInput" placeholder="Podaj jeszcze raz hasło"> </div> <button type="submit" id="registerBtn" class="btn btn-primary mb-3 animation">Zarejestruj</button> </form> </div>
W tym formularzu mamy trzy pola. Użytkownik będzie musiał potwierdzić wprowadzone przez siebie hasło, podając je jeszcze raz. Podobnie jak w poprzednim widoku tu także każdy element formularza posiada identyfikator, umożliwiający proste przechwycenie go w kodzie JS.
Ostatni widok jaki nam został do stworzenia to dashboard.
<div id="dashboardView"> <div class="d-flex flex-column justify-content-center align-items-center"> <div class="dashboard-item"> <img src="./assets//avatar.png" alt="avatar"> </div> <div class="dashboard-item"> <span id="user-name">adres email</span> </div> <div class="dashboard-item"> <button type="button" id="dashboardSignOutBtn" class="btn btn-outline-danger animation">Wyloguj</button> </div> </div> </div>
W tym widoku mamy tylko jeden element interaktywny, którym jest przycisk odpowiedzialny za wylogowanie użytkownika. Poza tym wyświetlany jest obrazek domyślnego awatara oraz adres e-mail zalogowanego użytkownika.
Do poprawnego wyświetlania dashboardu musimy jeszcze dodać kod css.
/* DASHBOARD */ #dashboardView { height: auto; font-size: 32px; } #dashboardView img { max-width: 114px; border-radius: 50%; border: solid 2px rgb(195, 199, 202); } .dashboard-item { padding: 10px; } .dashboard-item:first-child { padding-top: 20px; } .dashboard-item:last-child { padding-bottom: 20px; width: 100%; } /* ANIMATION */ .animation { -webkit-transition: all .3s ease; -moz-transition: all .3s ease; -o-transition: all .3s ease; transition: all .3s ease; }
Kod ten odpowiada za nadanie odpowiednich paddingów oraz wielkości elementom wewnątrz widoku, a także nadanie awatarawi okrągłego kształtu.
Implementacja funkcjonalności
Mając już kompletny widok, możemy teraz przystąpić do utworzenia klas, które będziemy wykorzystywać podczas implementacji aplikacji.
FirebaseConfig
To pierwsza klasa, którą utworzymy, będzie ona odpowiedzialna za inicjalizację Firebase.
class FirebaseConfig { static setup() { const config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "" }; firebase.initializeApp(config); } }
Wewnątrz klasy znajduję się statyczna metoda setup(), która zawiera obiekt konfiguracyjny oraz wywołaną metodę initializeApp() z obiektem konfiguracyjnym przekazanym w parametrze.
Obiekt konfiguracyjny jest indywidualny dla każdego projektu. Aby go odczytać dla wcześniej utworzonego przez nas projektu, należy przejść do strony z projektem, a następnie klikamy aplikacja sieciowa, oznaczona </>. Po podaniu nazwy oraz kliknięciu przycisku Zarejestruj aplikację zostanie wyświetlony obiekt konfiguracyjny, który należy wkleić do zmiennej config.
User
Klasa User reprezentować będzie dane wprowadzane przez użytkownika w formularzach.
class User { constructor(login, password, repeatPassword) { this.login = login; this.password = password; this.repeatPassword = repeatPassword; } }
UI
Klasa ta będzie odpowiedzialna za modyfikację drzewa DOM.
class UI { static authHeader() { return document.getElementById('authHeader'); } static dashboardHeader() { return document.getElementById('dashboardHeader'); } static loginView() { return document.getElementById('loginView'); } static registerView() { return document.getElementById('registerView'); } static dashboardView() { return document.getElementById('dashboardView'); } static loginHeaderBtn() { return document.getElementById('loginHeaderBtn'); } static registerHeaderBtn() { return document.getElementById('registerHeaderBtn'); } static showLoginView() { UI.authHeader().style.display = 'flex'; UI.dashboardHeader().style.display = 'none'; UI.loginView().style.display = 'block'; UI.registerView().style.display = 'none'; UI.dashboardView().style.display = 'none'; UI.loginHeaderBtn().classList.add('active'); UI.registerHeaderBtn().classList.remove('active'); } static showRegisterView() { UI.loginView().style.display = 'none'; UI.registerView().style.display = 'block'; UI.loginHeaderBtn().classList.remove('active'); UI.registerHeaderBtn().classList.add('active'); } static showDashboardView() { UI.authHeader().style.display = 'none'; UI.dashboardHeader().style.display = 'flex'; UI.loginView().style.display = 'none'; UI.registerView().style.display = 'none'; UI.dashboardView().style.display = 'block'; } static updateUsername() { document.getElementById('user-name').innerText = firebase.auth().currentUser.email; } static showMessageAlert(msg) { const alert = document.getElementById('messageAlert'); alert.innerText = msg; alert.style.display = 'block'; } static hideMessageAlert() { const alert = document.getElementById('messageAlert'); alert.innerText = ''; alert.style.display = 'none'; } static clearLoginFields() { document.getElementById('loginMailInput').value = ''; document.getElementById('loginPasswordInput').value = ''; } static clearRegisterFields() { document.getElementById('registerMailInput').value = ''; document.getElementById('registerPasswordInput').value = ''; document.getElementById('registerRepeatPasswordInput').value = ''; } }
Pierwszych siedem statycznych metod zwraca uchwyt do danych elementów z drzewa dom.
Metoda showLoginView() odpowiada za wyświetlenie jedynie widoku logowania oraz pogrubienie w nagłówku napisu Zaloguj.
Metoda showRegisterView() wyświetla widok rejestracji oraz zaznacza w nagłówku napis Zarejestruj.
Metoda showDashboardView() wyświetla dashboard, ukrywa nagłówek wyświetlany w widokach autoryzacji oraz wyświetla nagłówek przygotowany dla tego widoku.
Metoda updateUsername() odpowiedzialna jest za aktualizację wyświetlanego w dashboardzie adresu e-mail, obecnie zalogowanego użytkownika.
Metody showMessageAlert() oraz hideMessageAlert() wyświetlają i ukrywają komunikat o błędzie.
Ostatnie dwie metody czyszczą formularze. Odpowiednio formularz logowania oraz formularz rejestracji.
Tworzenie aplikacji
Mając już przygotowane klasy, możemy przystąpić do implementacji funkcjonalności aplikacji.
Na początek wywołujemy metodę inicjalizującą Firebase.
FirebaseConfig.setup();
Następnie po załadowaniu strony należy sprawdzić, czy użytkownik jest obecnie zalogowany czy też nie.
document.addEventListener('DOMContentLoaded', () => { firebase.auth().onAuthStateChanged(user => { if (user) { UI.updateUsername(); UI.showDashboardView(); } else { UI.showLoginView(); } }); });
JavaScript umożliwia nam wykonanie operacji zaraz po załadowaniu strony. Do tego celu wykorzystuje się zdarzenie DOMContentLoaded. Aby sprawdzić stan użytkownika należy wywołać obserwatora onAuthStateChanged(), który to przy każdej zmianie stanu będzie się wykonywał. To znaczy, że gdy się zalogujemy lub wylogujemy metoda ta wykona się kolejny raz. Sprawdzamy czy zwrócony przez nią obiekt jest różny od null.
Jeżeli tak, to znaczy, że użytkownik jest zalogowany, więc wywołujemy aktualizację adresu e-mail w widoku dashboardu, a następnie wyświetlamy go użytkownikowi. Jeśli jednak warunek nie jest spełniony wtedy wyświetlany jest widok logowania.
W widoku logowania mamy do obsłużenia dwa zdarzenia: kliknięcie na napis Zaloguj i Zarejestruj.
document.getElementById('loginHeaderBtn').addEventListener('click', () => { UI.showLoginView(); });
Aby obsłużyć kliknięcie w nagłówek Zaloguj przechwytujemy go, a następnie dodajemy nasłuchiwanie na zdarzenie kliknięcia. W momencie zajścia tego zdarzenia wywołujemy statyczną metodę showLoginView().
Podobnie robimy z obsługą kliknięcia w nagłówek Zarejestruj.
document.getElementById('registerHeaderBtn').addEventListener('click', () => { UI.showRegisterView(); });
Zmianie ulega jedynie wywoływana metoda. Tym razem wywołujemy showRegisterView().
Teraz pozostało nam zaimplementować funkcję związane z autoryzacją. Zaczynamy od rejestracji użytkownika.
document.getElementById('registerBtn').addEventListener('click', event => { event.preventDefault(); const login = document.getElementById('registerMailInput').value; const passwd = document.getElementById('registerPasswordInput').value; const repasswd = document.getElementById('registerRepeatPasswordInput').value; const user = new User(login, passwd, repasswd); if (!user.login || !user.password || !user.repeatPassword) { UI.showMessageAlert('Wypełnij pola'); return; } if (user.password !== user.repeatPassword) { UI.showMessageAlert('Hasła różnią się'); return; } firebase.auth().createUserWithEmailAndPassword(user.login, user.password) .then(response => { UI.hideMessageAlert(); UI.clearRegisterFields(); }) .catch(error => { UI.showMessageAlert(error.message); }); });
Przechwytujemy przycisk Zarejestruj, a następnie nasłuchujemy zdarzenia kliknięcia. Blokujemy standardowe działanie przycisku typu submit za pomocą metody preventDefault(). Następnie pobierane są wartości z pól formularza i tworzony jest obiekt User. Sprawdzamy czy pola zostały poprawne wypełnione, jeśli nie, wyświetlamy komunikat z błędem i kończymy wykonywanie funkcji. W kolejnym kroku sprawdzamy czy podane hasła są takie same, jeśli nie również wyświetlany jest komunikat. Ostatnim krokiem jest wywołanie metody createUserWithEmailAndPassword() z modułu auth(). W parametrach przekazujemy adres e-mail oraz hasło. Jeżeli poprawnie zostanie utworzone konto, ukryty zostanie komunikat z błędem, w razie gdyby był wyświetlony oraz pola zostaną wyczyszczone z wprowadzonych danych przez użytkownika. Obserwator automatycznie przeniesie nas do dashboardu, ponieważ użytkownik zostanie od razu zalogowany.
Logowanie w dużym stopniu jest podobne do rejestracji.
document.getElementById('loginBtn').addEventListener('click', event => { event.preventDefault(); const login = document.getElementById('loginMailInput').value; const passwd = document.getElementById('loginPasswordInput').value; const user = new User(login, passwd, null); if (!user.login || !user.password) { UI.showMessageAlert('Wypełnij pola'); return; } firebase.auth().signInWithEmailAndPassword(user.login, user.password) .then(response => { UI.clearLoginFields(); UI.hideMessageAlert(); UI.updateUsername(); }) .catch((error) => { UI.showMessageAlert(error.message); }); });
Również jest blokowane standardowe działanie przeglądarki, pobierane są wartości z formularza i tworzony jest obiekt User. Następnie sprawdzany jest warunek, czy któreś z pól nie jest puste. Różnica dopiero pojawia się przy wywoływaniu metody modułu auth(). Tym razem wywołujemy metodę signInWithEmailAndPassword() przekazując w parametrach adres e-mail oraz hasło. W chwili poprawnego logowania czyszczone są pola formularza, ukrwyany jest komunikat oraz aktualizowany jest adres e-mail obecnie zalogowanego użytkownika w dashboardzie. W przypadku niepowodzenia logowania wyświetlany jest komunikat z błędem dostarczonym przez Fireabse.
Przy logowaniu mamy również możliwość określenia trwałości uwierzytelnienia. Do dyspozycji mamy trzy tryby:
- local – stan uwierzytelnienia zostanie zachowany nawet po zamknięciu okna przeglądarki. Wyczyszczenie stanu może zostać jedynie dokonane poprzez jawne wylogowanie użytkownika. Jest to domyślny typ trwałości stanu uwierzytelnienia.
- session – stan uwierzytelnienia będzie zachowany jedynie w bieżącej sesji lub karcie. Wyczyszczenie stanu nastąpi po zamknięciu karty lub okna przeglądarki.
- none – stan zostanie wyczyszczony po odświeżeniu karty.
W przypadku kiedy chcielibyśmy zmienić trwałość z local na session, kod wyglądałby tak jak poniżej.
document.getElementById('loginBtn').addEventListener('click', event => { event.preventDefault(); const login = document.getElementById('loginMailInput').value; const passwd = document.getElementById('loginPasswordInput').value; const user = new User(login, passwd, null); if (!user.login || !user.password) { UI.showMessageAlert('Wypełnij pola'); return; } firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION) .then(() => { return firebase.auth().signInWithEmailAndPassword(user.login, user.password) .then(response => { UI.clearLoginFields(); UI.hideMessageAlert(); UI.updateUsername(); }); }) .catch(function (error) { UI.showMessageAlert(error.message); }); });
Ustawiamy za pomocą metody setPersistence() przekazując w parametrze typ.
Ostatnią funkcją tworzonej aplikacji jaką musimy zaimplementować jest wylogowywanie.
document.getElementById('dashboardSignOutBtn').addEventListener('click', e => { e.preventDefault(); firebase.auth().signOut() .then(response => { UI.hideMessageAlert(); }) .catch(error => { UI.showMessageAlert(error.message); }); });
Dodajemy zdarzenie kliknięcia do przycisku, a następnie blokujemy domyśle zachowanie przeglądarki. Po czym wywołujemy metodę signOut() będącą częścią modułu auth(). Jeśli wylogowywanie powiedzie się ukrywany jest komunikat o błędzie. W przypadku niepowodzenia wyświetlany komunikat z błędem.
Podsumowanie
W prosty sposób utworzyliśmy aplikację do logowania, wykorzystując do tego jedną z wielu metod jakie oferuje Firebase. Aplikacja została napisana w czystym JavaScriptcie, korzystającym ze standardu ES6. W kolejnych wpisach będziemy bazować na powstałej aplikacji. Dzięki temu wiele elementów będzie takich samych, a my będziemy mogli skupić się na poznawaniu kolejnych funkcjonalności omawianej platformy.
Kod całej aplikacji można znaleźć w moim repozytorium GitHub na branchu master w katalogu L1_auth_email.
Wpis ten powstał na podstawie dokumentacji Firebase
Jeśli chcesz zapoznać się z dokumentacją do omawianej autoryzacji, przejdź tu.
W celu zapoznania się z możliwymi komunikatami o błędzie zwracanymi przez Firebase, przejdź tu.
Zachęcam każdego do polubienia mojego FanPage oraz obserwowania na Twitterze. Dzięki temu nie ominie Cie żaden wpis na blogu.
Ciekawy tekst i na pewno przydatny. Szkoda tylko że pokazujesz złe praktyki dotyczące manipulacji widocznymi elementami HTML. Nie prościej byłoby dodać odpowiednią klasę do znacznika i ostylować w CSS, zamiast robić modyfikacje w stylach inline’owych dla każdego pojedynczego elementu?
Użytkownik niezalogowany nie widzi dashboardu na stronie, ale może całą jego zawartość ujrzeć przez opcje deweloperskie przeglądarki.