U nastavku ovog blog posta pokazati ću kako napraviti jednostavnu Angular web aplikaciju sa funkcionalnom SMS prijavom.
Prednosti korištenja SMS prijave:
– Sprečavanje lažnih korisnika – korištenjem SMS prijave onemogućava se kreiranje lažnih korisnika jer svaki korisnik koji se želi prijaviti treba imati jedinstveni broj telefona na koji će dobiti kod pomoću kojega će se prijaviti.
– Daje dodatnu vrijednost bazi korisnika – svaki potvrđeni korisnik koji stoji iza svakog od tih brojeva telefona predstavlja određenu vrijednost jer možemo biti sigurni da to nije lažni korisnik.
– Povećana sigurnost i poboljšava korisničko iskustvo – u današnje vrijeme korisnici koriste jako puno raznih aplikacija i postaje sve teže pratiti login podatke svake od njih. Omogućavanjem SMS prijave korisnik ne mora smišljati novo korisničko ime ili lozinku nego je samo dovoljno da uz sebe ima svoj mobitel kako bi se uspješno prijavio. Također, treća strana se ne može prijaviti kao npr. ja ako uz sebe nema moj mobilni telefon da pročita kod čime se drastično povećava sigurnost.
Kreiranje projekta
Kreiram novi Angular web projekt koristeći Angular CLI.
1 2 |
$ ng new angular-firebase-sms-login $ cd angular-firebase-sms-login |
Google Firebase
Nakon što sam kreirao Angular projekt mogu se prebaciti na Google Firebase i ondje postaviti projekt kako bi uopće mogao koristiti SMS login.
Kroz nekoliko sekundi Firebase projekt će biti spreman.
Unutar izbornika Develop – Authentication potrebno je odabrati Sign-in-method tab. Ondje se nalazi popis opcija dostupnih za login/prijavu.
Ja ću, naravno, ovaj put odabrati Phone.
Allow users to sign in with a mobile phone number, using Firebase SDK phone verification and user authentication tools.
Phone tj. SMS prijava je aktivirana.
Ako sada u izborniku Authentication – Users pogledam popis korisnika on će biti prazan jer za sada još nisam napravio niti jednu prijavu. Nakon što se sa bilo kojim brojem mobitela prvi put prijavim taj će se broj prikazati kao novi korisnik.
Prema trenutnim postavkama moj će projekt raditi ako web aplikaciju pokrenem na adresi http://localhost:4200/.
Međutim ako imam neku (pod)domenu kao npr. test.angularaplikacija.com ondje prijava putem SMS-a neće raditi dok ručno ne odobrim tu (pod)domenu.
To use Firebase Authentication in a web app, you must whitelist the domains that the Firebase Authentication servers can redirect to after signing in a user.
By default, localhost and your Firebase project’s hosting domain are whitelisted. You must whitelist the full domain names of any other of your web app’s hosts. Note: whitelisting a domain allows for requests from any URL and port of that domain.
Prije nego se vratim svojem Angular projektu trebaju mi još neki podaci bez kojih moj Angular projekt ne bi znao kako se povezati s Firebaseom.
Ovdje idem na Project settings.
Nakon toga idem na Add Firebase to your web app.
I konačno, ovdje imam konfiguracijski config objekt koju ću kasnije postaviti u svoj Angular projekt i tako ga povezati sa Firebaseom.
Implementacija prijave/logina
Sada se vraćam u svoj Angular projekt.
AppComponent ću malo prilagoditi tj. dodati joj nekoliko detalja kojih trenutno nema.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Component, OnInit, Injectable } from '@angular/core'; @Injectable() @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'angular-firebase-sms-login'; constructor() { } ngOnInit() { } } |
Trebat će mi window objekt pa ću napraviti servis za njega kako bi ga mogao pozivati u cijelog projektu sa jednog mjesta.
1 |
$ ng g service window |
U servisu se nalazi sljedeće:
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@angular/core'; @Injectable() export class WindowService { get windowRef() { return window } } |
Sada sam spreman za dodavanje Firebasea unutar projekta, a to radim naredbama:
1 2 |
$ npm install --save firebase $ npm install angularfire2 firebase --save |
Sada sve to trebam navesti unutar app.module.ts datoteke. Ovdje moram uvesti FormsModule zato što ću u login formi koristiti [(ngModel)].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { FormsModule } from '@angular/forms'; import { AngularFireModule } from 'angularfire2'; import { AngularFireAuthModule } from 'angularfire2/auth'; export const firebaseConfig = { apiKey: "***************************************", authDomain: "angular-firebase-sms-login.firebaseapp.com", databaseURL: "https://angular-firebase-sms-login.firebaseio.com", projectId: "angular-firebase-sms-login", storageBucket: "angular-firebase-sms-login.appspot.com", messagingSenderId: "************" }; @NgModule({ declarations: [ AppComponent, DashboardComponent ], imports: [ BrowserModule, AngularFireModule.initializeApp(firebaseConfig), AngularFireAuthModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Na ekranu će to izgledati ovako:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="> <div [hidden]="user" style="text-align:center"> <label for="phone">Broj mobitela:</label><br> <input type="text" [(ngModel)]="phoneNumber.country" class="input" placeholder="+385" maxlength="4"> <input type="text" [(ngModel)]="phoneNumber.line" class="input" placeholder="9********" maxlength="9"> <div id="recaptcha-container"></div> <button (click)="sendLoginCode()">Pošalji SMS</button> <div *ngIf="windowRef.confirmationResult"> <hr> <label for="code">Ovdje unesite svoj pin</label><br> <input type="text" name="code" [(ngModel)]="verificationCode"> <button (click)="verifyLoginCode()">Potvrdi</button> </div> </div> <div *ngIf="user"> <p>Prijava uspješna!</p> <p>UserId: {{ user?.uid }}</p> </div> </div> |
Funkcionalnost se može vidjeti u nastavku, a sastoji se od nekoliko funkcija. Osnova svega je broj mobitela koji je u E.164 obliku.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import { Component, OnInit, Injectable } from '@angular/core'; import { WindowService } from './window.service'; import { AngularFireAuth } from 'angularfire2/auth'; import * as firebase from 'firebase/app'; export class PhoneNumber { country: string = "+385"; line: string; get e164() { const num = this.country + this.line return `+${num}` } } @Injectable() @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [WindowService] }) export class AppComponent implements OnInit { title = 'Angular Firebase SMS Login'; windowRef: any; phoneNumber = new PhoneNumber() verificationCode: string; user: any; constructor( private win: WindowService, public _angularAuth: AngularFireAuth) { } ngOnInit() { this.windowRef = this.win.windowRef; this.windowRef.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container') this.windowRef.recaptchaVerifier.render(); } sendLoginCode() { const appVerifier = this.windowRef.recaptchaVerifier; const num = this.phoneNumber.e164; firebase.auth().signInWithPhoneNumber(num, appVerifier) .then(result => { this.windowRef.confirmationResult = result; }) .catch( error => console.log(error) ); } verifyLoginCode() { this.windowRef.confirmationResult .confirm(this.verificationCode) .then( result => { this.user = result.user; console.log(this.user); }) .catch( error => console.log(error, "Pogrešan kod?")); } } |
U praksi to izgleda ovako:
Klikom na “Pošalji SMS” šalje se SMS poruka na uneseni broj mobitela.
Nakon unosa koda iz SMS-a uspješno se prijavljujem.
U slučaju da se idem nekoliko puta za redom prijaviti i Google Firebase posumnja na zloupotrebu pojavit će se napredna reCAPTCHA.
Tek sada, nakon što je prijava uspješno prošla, mogu svoj broj mobitela vidjeti unutar Google Firebase sučelja.
Zaključak
Ovo je bio primjer jednostavne Angular web aplikacije sa SMS prijavom gdje se sva logika odvija na klijentskoj strani. Nedavno sam napravio i verziju gdje se sva logika odvija na serverskoj strani unutar NodeJS-a sa dodatnim sigurnosnim provjerama. Bilo bi previše za ovaj blog post da sam išao i taj dio obraditi. Možda u nekom od sljedećih blog postova, ako netko od posjetitelja bloga bude zainteresiran.
Osim SMS prijave obradio sam nekoliko tema s ostalim vrstama prijave kao što su:
– Angular & Firebase – registracija i prijava
– Ionic 3 – Google login/prijava
– Ionic 3 – Facebook prijava
– Ionic 3 – Firebase registracija i prijava
Struktura projekta prema package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
{ "name": "angular-firebase-sms-login", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "~7.1.0", "@angular/common": "~7.1.0", "@angular/compiler": "~7.1.0", "@angular/core": "~7.1.0", "@angular/forms": "~7.1.0", "@angular/platform-browser": "~7.1.0", "@angular/platform-browser-dynamic": "~7.1.0", "@angular/router": "~7.1.0", "angularfire2": "^5.1.1", "core-js": "^2.5.4", "firebase": "^5.7.2", "rxjs": "~6.3.3", "tslib": "^1.9.0", "zone.js": "~0.8.26" }, "devDependencies": { "@angular-devkit/build-angular": "~0.11.0", "@angular/cli": "~7.1.4", "@angular/compiler-cli": "~7.1.0", "@angular/language-service": "~7.1.0", "@types/node": "~8.9.4", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.3", "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~3.1.1", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~1.1.2", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.11.0", "typescript": "~3.1.6" } } |