Tempo di lettura: 15 minuti
In questa breve guida imparerete semplicemente ed in pochi passaggi come realizzare un sistema di autenticazione con Ionic ed Angular con il server express di nodejs.
Fino alla versione 3 dello stesso questi era implementabile solo con il framework di casa google, dalla 4 in poi diventa, per così dire, indipendente in quanto si può utilizzare con altre librerie come ReactJs , VueJs o anche con javascript puro per realizzare web app progressive.
Vedrete come Ionic sfrutti il lazy loading ossia il caricamento pigro dei componenti in Angular.
Utilizzeremo come al solito la libreria HttpClient
per inviare richieste POST a un server di autenticazione back-end creato con Node ed Express.js ed il BehaviorSubject
della libreria RxJS per tenere traccia dello stato di autenticazione.
Impareremo come utilizzare il modulo Ionic Storage per salvare le informazioni JWT nel nostro localstorage.
Requisiti ed occorrente
Prima di procedere, devi assicurarti di avere Node.js e NPM installati sul tuo computer di sviluppo. In caso contrario scaricali dal sito ufficiale .
Per quanto riguarda l’Ide vi consiglio l’utilizzo di visual studio code , ide open source ed estendibile con migliaia di estensioni.
E’chiaro che dovete conoscere Angular, altrimenti è inutile seguire il tutorial, in caso contrario vi consiglio il mio corso su Udemy.
Ionic ed Angular: installa ionic/cli e crea progetto ionic
Prima di sporcarci le mani con il codice vi ricordo e vi consiglio di consultare sempre la documentazione ufficiale di Ionic.
Come prima cosa installate la cli di Ionic , da terminale digitate:
npm install -g ionic
in seguito potete creare il vostro primo progetto con Ionic, scegliete un percorso a piacere nel vostro pc e da terminale digitate:
ionic start auth-demo blank --type=angular
In pratica gli diciamo di chiamare il progetto auth-demo, questi avrà un template home vuoto (blank) e di utilizzare Angular.
Una volta terminato aprite il progetto, noterete che la struttura è simile a quella di un progetto Angular:
per avviare il server da terminale digitate:
ionic serve -o
Ionic ed Angular: realizza il modulo auth
Bene, realiziamo ora il modulo che incapsula le pagine che gestiranno l’autenticazione. Vi ricordo che Ionic sfrutta il Lazy Loading, per cui tutti i componenti hanno un loro modulo (se non sai di cosa sto parlando dai un’occhiata al mio corso).
Daterminale digitate:
ionic generate module auth
verrà creato il file src/app/auth/auth.module.ts:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; @NgModule({ declarations: [], imports: [ CommonModule ] }) export class AuthModule { }
Come potete notare questi importa CommonModule il quale supporta tutte le direttive e pipe di Angular.
Ora importatelo nel modulo radice dell’applicazione, quindi in app.module.ts:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import {AuthModule} from './auth/auth.module' @NgModule({ declarations: [AppComponent], entryComponents: [], imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,AuthModule], providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], bootstrap: [AppComponent], }) export class AppModule {}
visto che ci siamo importa anche gli altri moduli necessari come HttpClient e FormsModule nel file auth.module.ts :
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; FormsModule @NgModule({ declarations: [], imports: [ CommonModule, HttpClientModule, FormsModule ] }) export class AuthModule { }
Ionic Storage per salvare i dati in locale
Ionic ci fornisce il modulo Ionic Storage per salvare i dati in locale, come nel localStorage, quindi aprite il terminale e scaricatevi il package con npm:
npm install @ionic/storage-angular
ora se aprite il file package.json:
... "@ionic/storage/angular": "^3.0.6", ...
noterete la versione attuale dello stesso, la 3.0.6.
Ora importatelo nel file auth.module.ts:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { IonicStorageModule } from '@ionic/storage-angular'; @NgModule({ declarations: [], imports: [ CommonModule, HttpClientModule, FormsModule, IonicStorageModule.forRoot() ] }) export class AuthModule { }
Crea il service e l’interface
Prima di creare il service per l’autenticazione realiziamo il model per i dati , quindi l’interface:
ionic g interface auth/user
questo comando realizzerà l’interfaccia User all’interno della directory Auth. Aprite il file src/app/auth/user.ts e modificate:
export interface User { id: number; name: string; email: string; password: string; }
ora realiziamo il model relativo alla risposta del server, da terminale digitate:
ionic g interface auth/auth-response
Aprite il file src/app/auth/auht-response.ts e modificate:
export interface AuthResponse { user: { id: number; name: string; }; accessToken: string; expiresIn: number; }
questo è relativo alla risposta restituita dal server di autenticazione.
Non ci rimane che realizzare il service, da terminale digitate:
ionic g service auth/auth
Come potete notare all’interno della folder auht vengono generati 2 files, aprite auth.service.ts ed aggiungete come di seguito:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage-angular'; import { BehaviorSubject, Observable } from 'rxjs'; import { User } from './user'; import { AuthResponse } from './auth-response'; import { tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor() { } }
- In pratica importiamo HttpClient per gestire richieste Post con il server che andremo a realizzare
- Storage per la gestione del salvataggio in locale del Token restituito dal server;
- Observable per la gestione delle operazioni asincrone come in Angular;
- L’operatore Tap il quale gestisce l’esecuzione degli effetti collaterali durante la sottoscrizione degli observable restituiti dal server;
BehaviorSubject
rappresenta, infatti, un tipo diSubject
più flessibile e semplice da utilizzare rispetto agli Oservable;- I model User ed AuthResponse;
Ora dichiarate questa variabili sempre all’interno del service dopo gli import:
const $ApiUrl = 'http://localhost:3000';
variabile relativa all’indiizzo del server che creeremo in seguito.
Quindi all’interno della classe dello stesso dichiarate:
... export class AuthService { authSubject = new BehaviorSubject(false); ...
BehaviorSubject è un tipo di Observable , come citato in precedenza, che verrà utilizzato per la sottoscrizione dello stato di autenticazione.
Ok ora iniettate all’interno del costruttore.
... export class AuthService { authSubject = new BehaviorSubject(false); constructor(private httpClient: HttpClient, private strage: Storage) { } } ...
Ionic ed Angular: invio richiesta Post al server, metodo di registrazione
Sempre nel service aggiungiamo il metodo relativo alla registrazione dell’utente:
register(user: User): Observable<AuthResponse> { return this.httpClient.post<AuthResponse>(`${$ApiUrl}/register`, user).pipe( tap(async (res: AuthResponse)=> { if(res.user){ await this.storage.create(); await this.storage.set('accessToken', res.accessToken); await this.storage.set('expiresIn', res.expiresIn); this.authSubject.next(true); console.log(res.accessToken); } }) ); }
In questo script utiliziamo il metodo Post() per inviare una richiesta Post al server (a breve lo creeremo) che verrà eseguito all’indirizzo localhost::3000.
Il metodo pipe() ci consente di concatenare più operatori, nel caso nostro effettuiamo un effetto collaterale relativo alla memorizzazione del JWT (token di accesso e data di scadenza) nell’archivio locale, quindi l’operatore Tap della libreria Rxjs.
Nell’operatore Tap() controlliamo se la response contiene un oggetto user ed impostiamo il token di accesso con una data di scadenza in accesToken ed exipressIn, quindi emettiamo un valore per l’authSubject del metodo next().
Il metodo di login
Impostato il metodo di registrazione dobbiamo realizzare quello di login, sempre nel service aggiungete:
login(user: User): Observable<AuthResponse> { return this.httpClient.post<AuthResponse>(`${$ApiUrl}/login`, user).pipe( tap(async (res: AuthResponse) => { if (res.user) { await this.storage.set('accessToken', res.accessToken); await this.storage.set('expiresIn', res.expiresIn); this.authSubject.next(true); } }) ); }
come si nota è simile al metodo di registrazione tranne la url che questa volta punta su login.
Il metodo di logout
Il metodo di logout rimuoverà i dati di autenticazione JWT dall’archivio locale, nel service dopo il metodo di login aggiungete:
async logout(){ await this.storage.remove('accessToken'); await this.storage.remove('expiresIn'); this.authSubject.next(false); }
emettiamo un valore impostato a false nel BehaviorSubject per cancellare i dati.
Ottieni lo stato di autenticazione
Dopo il metodo di logout aggiungiamo il metodo isLoggedIn per verificare , tramite Id, se l’utente sia autenticato o meno:
isLoggedIn() { return this.authSubject.asObservable(); }
restituiamo semplicemente la variabile cast authSubject ad un Observable per verificare che l’utente è autenticato.
Ionic ed Angular: Realizza le pagine
Ora è giunto il momento di creare le pagine di registrazione e login, iniziamo con quella di registrazione:
da terminale digita:
ionic g page auth/register
anche questa verrà creata all’interno della folder auth e genererà una serie di file come di seguito:
come in Angular crea il componente, il template html, lo stile ed il modulo.
Avviate il server digitando da terminale:
ionic serve -o
e navigate all’indirizzo http://localhost:8100/register:
al momento la pagina è vuota però si evince il titolo register nella toolbar.
Il componente viene aggiunto in automatico in app-routing.module.ts:
... const routes: Routes = [ { path: '', component: RegisterPage } ]; ...
semplice non trovate? 🙂 🙂
Ora Apri il la classe di register e cioè register.page.ts ed aggiungi:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './../auth.service'; @Component({ selector: 'app-register', templateUrl: './register.page.html', styleUrls: ['./register.page.scss'], }) export class RegisterPage implements OnInit { constructor(private authService: AuthService, private router : Router) { } ngOnInit() { } }
abbiamo iniettato nel costruttore le dipendenze ad AuthService ed a Router.
Ora possiamo realizzare il metododi registrazione utenti connesso con il service:
... register(form: any){ this.authService.register(form.value).subscribe((res)=>{ this.router.navigateByUrl('home'); }); } ...
Questo metodo chiama semplicemente il metodo register del service, ci si iscrive all’Observable dopo di che veniamo reindirizzati alla Home mediante il metodo navigateByUrl della libreria Router.
Bene niente di così complicato, è arrivato il momento di realizzare la vista relativa a register, apri il file register.page.html ed aggiungi:
<ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-back-button></ion-back-button> </ion-buttons> <ion-title>Register</ion-title> </ion-toolbar> </ion-header> <ion-content> <form #form="ngForm" (ngSubmit)="register(form)"> <ion-item lines="full"> <ion-label position="floating">Your Name</ion-label> <ion-input name="name" ngModel type="text" required></ion-input> </ion-item> <ion-item lines="full"> <ion-label position="floating">Email</ion-label> <ion-input type="email" name="email" ngModel required></ion-input> </ion-item> <ion-item lines="full"> <ion-label position="floating">Password</ion-label> <ion-input type="password" required name="password" ngModel></ion-input> </ion-item> <ion-item lines="full"> <ion-label position="floating">Confirm Password</ion-label> <ion-input type="password" required name="confirm" ngModel></ion-input> </ion-item> <ion-row> <ion-col> <ion-button type="submit" color="danger" expand="block">Sign Up</ion-button> </ion-col> </ion-row> </form> </ion-content>
Aggiungiamo la direttiva Angular [routerLink]="['...']"
nei pulsanti di Ionic per abilitare la navigazione tra i componenti.
Visto che ci siamo aprite il file home.page.html e aggiungete il codice seguente:
<ion-header> <ion-toolbar> <ion-title> Ionic Form </ion-title> </ion-toolbar> </ion-header> <ion-content class="auth-form"> <ion-grid> <ion-row> <ion-col align-self-center> <ion-button [routerLink]="['/register']" expand="block" color="primary">Register</ion-button> <span class="divider line one-line">or</span> <span class="already">Already a user?</span> <ion-button [routerLink]="['/login']" expand="block" color="danger">Sign In</ion-button> </ion-col> </ion-row> </ion-grid> </ion-content>
ora realizza la pagina di login, da terminale digita:
ionic g page auth/login
anche in questo caso verranno creati diversi file e verrà aggiunto il percorso in app-routing.module.ts.
Apri il file login.page.ts ed aggiungi:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from '../auth.service'; @Component({ selector: 'app-login', templateUrl: './login.page.html', styleUrls: ['./login.page.scss'], }) export class LoginPage implements OnInit { constructor(private authService: AuthService, private router: Router) { } ngOnInit() { } }
quindi realizza il metodo di login:
... login(form: any) { this.authService.login(form.value).subscribe((res) => { this.router.navigateByUrl('home'); }); } ...
quindi crea la view, apri il file login.page.html ed aggiungi:
<ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-back-button></ion-back-button> </ion-buttons> <ion-title>Log In</ion-title> </ion-toolbar> </ion-header> <ion-content> <form #form="ngForm" (ngSubmit)="login(form)"> <ion-item lines="full"> <ion-label position="floating">Email</ion-label> <ion-input name="email" type="text" required ngModel></ion-input> </ion-item> <ion-item lines="full"> <ion-label position="floating">Password</ion-label> <ion-input name="password" type="password" required ngModel></ion-input> </ion-item> <ion-row> <ion-col> <ion-button type="submit" color="danger" expand="block">Sign In</ion-button> <a [routerLink]="['/forgot-password']" class="small-text">Forgot Password?</a> </ion-col> </ion-row> </form> </ion-content>
realizza la pagina forgot-password, da terminale digita:
ionic g page auth/forgot-password
quindi crea la view, apri il file auth/forgot-password.page.html ed aggiungi:
<ion-header> <ion-toolbar> <ion-buttons slot="start"> <ion-back-button></ion-back-button> </ion-buttons> <ion-title>Reset Your Password</ion-title> </ion-toolbar> </ion-header> <ion-content> <form> <ion-item lines="full"> <ion-label position="floating">Email</ion-label> <ion-input type="email" required></ion-input> </ion-item> <ion-row> <ion-col> <ion-button type="submit" color="danger" expand="block">Send</ion-button> </ion-col> </ion-row> <small> Please provide the username or email address that you used when you signed up for your Evernote account. </small> </form> </ion-content>
la classe la modificheremo in seguito.
Realizza il server di autenticazione con Express.js
Crea una directory dove inserire il codice relativo al server, naviga al suo interno ed inizializza il progetto realizzando il file package.json, da terminale digita:
mkdir server cd server npm init -y
quindi installa le dipendenze:
npm install express body-parser sqlite3 bcryptjs jsonwebtoken cors
verrà creata, all’interno delle root del progetto, la cartella node_modules con tutti i package scaricati ed il file package.json ora si presenta così:
{ "name": "server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.19.2", "cors": "^2.8.5", "express": "^4.17.3", "jsonwebtoken": "^8.5.1", "sqlite3": "^5.0.2" } }
notate le dipendenze con le varie versioni installate.
Sempre nella root principale del progetto realizzate il file index.js ed aggiungete il seguente codice:
"use strict"; const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors') const sqlite3 = require('sqlite3').verbose(); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const SECRET_KEY = "secretkey23456"; const app = express(); const router = express.Router(); app.use(cors()) router.use(bodyParser.urlencoded({ extended: false })); router.use(bodyParser.json()); const database = new sqlite3.Database("./my.db"); const createUsersTable = () => { const sqlQuery = ` CREATE TABLE IF NOT EXISTS users ( id integer PRIMARY KEY, name text, email text UNIQUE, password text)`; return database.run(sqlQuery); } const findUserByEmail = (email, cb) => { return database.get(`SELECT * FROM users WHERE email = ?`,[email], (err, row) => { cb(err, row) }); } const createUser = (user, cb) => { return database.run('INSERT INTO users (name, email, password) VALUES (?,?,?)',user, (err) => { cb(err) }); } createUsersTable(); router.get('/', (req, res) => { res.status(200).send('This is an authentication server'); }); router.post('/register', (req, res) => { const name = req.body.name; const email = req.body.email; console.log(req.body); const password = bcrypt.hashSync(req.body.password); createUser([name, email, password], (err)=>{ if(err) return res.status(500).send("Server error!"); findUserByEmail(email, (err, user)=>{ if (err) return res.status(500).send('Server error!'); const expiresIn = 24 * 60 * 60; const accessToken = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: expiresIn }); res.status(200).send({ "user": user, "access_token": accessToken, "expires_in": expiresIn }); }); }); }); router.post('/login', (req, res) => { const email = req.body.email; const password = req.body.password; findUserByEmail(email, (err, user)=>{ if (err) return res.status(500).send('Server error!'); if (!user) return res.status(404).send('User not found!'); const result = bcrypt.compareSync(password, user.password); if(!result) return res.status(401).send('Password not valid!'); const expiresIn = 24 * 60 * 60; const accessToken = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: expiresIn }); res.status(200).send({ "user": user, "access_token": accessToken, "expires_in": expiresIn}); }); }); app.use(router); const port = process.env.PORT || 3000; const server = app.listen(port, () => { console.log('Server listening at http://localhost:' + port); });
se non conoscete express e vorreste saperne di più vi invito a seguire il mio corso su Udemy.
Aggiungi lo start nel nodo scripts del file package.json:
... "scripts": { "start": "node index.js", ... }
ora eseguite il server, da terminale digitate:
npm start
Una volta avviato il server avviate Postman (se non lo avete vi consiglio di scaricarvelo da quest’indirizzo) e nella barra degli indirizzi inserite l’indirizzo relativo al server http://localhost:3000/register ed impostate come di seguito:
passiamo i dati che si aspetta il server in formato json, fate click su send:
ed in basso , se tutto ok, noterete info relative all’utente appena registratosi con accessToken ed expiresToken impostato ad un giorno di durata. Bene funziona tutto, è arrivato il momento di provarlo nel browser, registrate un utente ed aprite la console:
Ottimo siamo riusciti a realizzare un sistema di registrazione con ionic ed il server Express, semplice vero?
Proviamo ora a loggarci con Postman inserendo , chiaramente, i dati relativi alla registrazione precedente:
fate click su send e voilà.Provate a mettere dei dati sbagliati e rifate click su send:
mi aspetto un errore , difatti!! 🙂 🙂
Conclusione
Siamo riusciti ad implementare un piccolo sistema di autenticazione con Ionic-Angular ed il server Express, certo è un sistema un pò spartano in quanto mancano diverse cose, ma il nostro scopo era quello di capire come implementare il framework e conoscere alcuni dei suoi componenti.
Se volete Approfondire le conoscenze su Angular potete seguire il corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!
Se volete Approfondire le conoscenze su REACT E NODEJS CON EXPRESS E MONGODB potete seguire il corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!