Tempo di lettura: 75 minuti
Creazione di un’API di accesso utilizzando Laravel
In questa sezione, creeremo un semplice endpoint API di accesso che useremo per recuperare un token Web JSON ( JWT ).
Cosa è JWT ? JWT è un protocollo di autenticazione , lo utilizziamo per trasferire in modo sicuro le informazioni in formato JSON. È molto simile alla sessione o al cookie, ma è senza stato; il che significa che non è necessario memorizzare alcuna sessione sul server. Questo tipo di autenticazione è noto anche come autenticazione basata su token .
Utilizzando l’ autenticazione basata su token , dovremo inviare una richiesta, che contiene le informazioni dell’utente, a un server di autenticazione. Il server verificherà la richiesta. Se le informazioni sull’utente sono corrette, il server genererà un nuovo token JWT e ce lo rispedirà. Una volta che abbiamo il token, possiamo usarlo per recuperare una risorsa sicura da qualsiasi luogo.
Suggerimento: a partire da Laravel 5.3, possiamo anche utilizzare la libreria Passport , che è costruita sopra il server League OAuth2 , per generare un token. Tuttavia, OAuth 2 è un framework e potrebbe essere necessario del tempo per apprenderlo. JWT è molto più facile da imparare.
Per usare JWT con Laravel, dobbiamo prima installare il pacchetto jwt-auth .
Esegui il seguente comando (nella radice della tua app Laravel) per scaricare e installare l’ultima versione di jwt-auth:
composer require tymon/jwt-auth "1.0.*"
Nota: attualmente, jwt-auth viene aggiornato per funzionare con l’ultima versione di Laravel. In caso di problemi, eseguire invece questo comando per installare la versione dev:
composer require tymon/jwt-auth:dev-develop --prefer-source
ora apri config/app.php e aggiungi le facades nel nodo aliases:
'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth', 'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory'
Quinfi puoi pubblicare il file di configurazione di questo pacchetto eseguendo questo comando da terminale:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Un nuovo file chiamato jwt.php verrà generato nella directory config . Ecco alcune opzioni che puoi configurare. Puoi impostare la chiave segreta nel file, oppure puoi usare questo comando per generare una nuova chiave:
php artisan jwt:secret
Il jwt-auth pacchetto è ora installato!
Puoi leggere ulteriori informazioni e imparare come installare il pacchetto qui .
Successivamente, dobbiamo aggiornare il nostro modello user per implementare il contratto Tymon \ JWTAuth \ Contracts \ JWTSubject . Apri app / User.php e trova:
class User extends Authenticatable;
sostituisci con:
class User extends Authenticatable implements AuthenticatableUserContract
sopra aggiungi:
use Tymon\JWTAuth\Contracts\JWTSubject as AuthenticatableUserContract;
non rimane che aggiungere i metodi:
public function getJWTIdentifier() { return $this->getKey(); } public function getJWTCustomClaims() { return []; }
ecco il file app/models/user.php aggiornato:
<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Tymon\JWTAuth\Contracts\JWTSubject as AuthenticatableUserContract; class User extends Authenticatable implements AuthenticatableUserContract { use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; public function getJWTIdentifier() { return $this->getKey(); } public function getJWTCustomClaims() { return []; } }
Documentazione: è possibile saperne di più sull’aggiornamento del modello utente su: http://jwt-auth.readthedocs.io/en/docs/quick-start/#update-your-user-model
Ora apriamo routes/api.php e creiamo una nuova rotta chiamata login :
Route::group(['prefix' => 'v1', 'middleware' => 'cors'], function(){ Route::resource('users', UserController::class); Route::post('register', 'App\Http\Controllers\AuthController@postRegister'); Route::post('login', 'App\Http\Controllers\AuthController@authenticate'); });
Come vedi, questo percorso viene gestito dal AuthController metodo authenticate (@authenticate). Allora apri il file AuthController.php e aggiungi il metodo:
AuthController.php
public function authenticate(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|email', 'password' => 'required', ]); if ($validator->fails()) { $message = ['errors' => $validator->messages()->all()]; $response = Response::json($message, 202); } else { $credentials = $request->only('email', 'password'); try { $token = JWTAuth::attempt($credentials); if ($token) { $message = ['success' => $token]; return $response = Response::json(["token" => $token], 200); } else { $message = ['errors' => "Invalid credentials"]; return $response = Response::json($message, 202); } } catch (JWTException $e) { return response()->json(['error' => 'could_not_create_token'], 500); } } }
Qui utilizziamo anche il Validator per verificare la richiesta. Il metodo JWTAuth :: try viene utilizzato per tentare di verificare l’utente. Se le credenziali sono corrette, verrà generato un nuovo token.
Ora possiamo controllare il token. Se il token non è vuoto (ciò significa che è stata creata una nuova chiave), invieremo una risposta positiva con il token indietro. In caso contrario, restituiremo un errore.
if ($token) { $message = ['success' => $token]; return $response = Response::json(["token" => $token], 200); } else { $message = ['errors' => "Invalid credentials"]; return $response = Response::json($message, 202); }
Usiamo anche try and catch per rilevare possibili errori di JWTException .
} catch (JWTException $e) { return response()->json(['error' => 'could_not_create_token'], 500); }
Nota: non è necessario utilizzare JWTException, è opzionale.
Non dimenticare di importare JWTAuth e JWTException :
Trova:
class AuthController extends Controller
Aggiungi sopra:
use Tymon\JWTAuth\Facades\JWTAuth; use Tymon\JWTAuth\Exceptions\JWTException;
Fatto! Ora se inviamo una richiesta con le credenziali corrette a http://127.0.0.1:8000/api/v1/login , riceverai un token!
Puoi utilizzare Postman per testare il nuovo endpoint API.
Suggerimento: questa tecnica può essere utilizzata con Vue.js, ReactJS o qualsiasi altro framework e applicazione che utilizza API.
Ottenere un JSON Web token utilizzando Angular
In questa sezione, creeremo un modulo di accesso, che viene utilizzato per inviare le credenziali di un utente all’endpoint dell’API Laravel e recuperare un jwt (json web token)
Iniziamo generando il component login . Vai alla directory dell’app ed esegui questo comando:
ng g c components/login
Verrà creato un nuovo LoginComponent !
Apri app-routing.modile.ts e aggiungi questo nuovo percorso:
{ path: 'login', component: LoginComponent},
ricordati di importarlo:
import {LoginComponent} from './components/login/login.component';
Successivamente, apri services/auth.service.ts e crea una nuova variabile chiamata token , che è di tipo String :
Trova:
export class AuthService {
aggiungi subito dopo:
token: string;
realizza il metodo di accesso:
login(user: User): Observable<any> { return this.http.post(' http://127.0.0.1:8000/api/v1/login', user) .pipe( map((response: any) => { const token = response.token; console.log('Response token:' + token); if (token) { this.token = token; localStorage.setItem('token', this.token); return true; } else { return false; } }), catchError(this.errorHandler) ); }
Questa funzione viene utilizzata per effettuare la nostra richiesta HTTP all’API di accesso. Se otteniamo una risposta, controlliamo se la risposta contiene il token:
const token = response.token; console.log("Response token:" + token); if (token) {
Se il token esiste, utilizziamo localStorage per archiviare il token localmente:
this.token = token; localStorage.setItem('token', this.token);
Successivamente, restituiamo true , dicendo che la richiesta di accesso è andata a buon fine.
return true;
Se il token non esiste, restituiamo semplicemente false :
} else { return false; }
È ora di aggiornare login.component.ts per aggiungere alcuni controlli del modulo e un nuovo metodo di accesso :
components/login.component.ts :
import { Component, OnInit } from '@angular/core'; import {FormBuilder, Validators, FormControl, FormGroup} from '@angular/forms'; import {Router} from '@angular/router'; import {AuthService} from '../../services/auth.service'; @Component({ selector: 'app-login', template: ` <p> login works! </p> `, styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { userForm: FormGroup; passwordControl: FormControl; emailControl: FormControl; token: string; status: string; message: string; static isValidEmail(control: FormControl): any { const emailRegexp = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; return emailRegexp.test(control.value) ? null : { invalidEmail: true }; } constructor(fb: FormBuilder, private authService: AuthService, private router: Router) { this.passwordControl = fb.control('', [Validators.required, Validators.minLength(6)]); this.emailControl = fb.control('', [Validators.required, LoginComponent.isValidEmail]); this.userForm = fb.group({ email: this.emailControl, password: this.passwordControl }); } ngOnInit(): void { } login(): void { this.authService.login(this.userForm.value) .subscribe( response => { console.log(response); if (response === true) { this.router.navigate(['/admin']); } else { this.status = 'error'; this.message = 'Username or password is incorrect'; } }, error => { console.log(error); this.status = 'error'; this.message = error[`message`]; } ); } }
Come puoi vedere, questo componente è molto simile a RegisterComponent . Anche il metodo di accesso è semplice. Usiamo il metodo authService.login () per inviare il modulo. Se la risposta è vera , reindirizziamo gli utenti all’area di amministrazione :
if (response === true) { this.router.navigate(['/admin']);
In caso contrario, viene visualizzato un messaggio di errore:
this.status = 'error'; this.message = 'Username or password is incorrect';
Ecco la vista di accesso:
components/login.component.ts
<div class="col-md-10 col-md-offset-1"> <div *ngIf="status=='success'" class="alert alert-success" role="alert"> {{ message }} </div> <div *ngIf="status=='error'" class="alert alert-danger" role="alert"> {{ message }} </div> <div class="well well bs-component"> <form novalidate class="form-horizontal" (ngSubmit)="login()" [formGroup]="userForm"> <fieldset> <legend>Login</legend> <div class="form-group"> <label for="email" class="col-lg-2 control-label">Email</label> <div class="col-lg-10"> <input type="text" class="form-control" id="email" name="email" placeholder="Email of the user" formControlName="email"> <div *ngIf="emailControl.dirty && emailControl.hasError('required')" class="alert alert-danger">Email is required</div> <div *ngIf="emailControl.dirty && emailControl.hasError('minlength')" class="alert alert-danger">Email should have at least 3 characters</div> <div *ngIf="emailControl.dirty && emailControl.hasError('invalidEmail')" class="alert alert-danger">Not a valid email</div> </div> </div> <div class="form-group"> <label for="password" class="col-lg-2 control-label">Password</label> <div class="col-lg-10"> <input type="password" class="form-control" id="password" name="password" placeholder="Password" formControlName="password"> <div *ngIf="passwordControl.dirty && passwordControl.hasError('required')" class="alert alert-danger">Password is required</div> <div *ngIf="passwordControl.dirty && passwordControl.hasError('minlength')" class="alert alert-danger">Password should have at least 6 characters</div> </div> </div> <div class="form-group"> <div class="col-lg-10 col-lg-offset-2"> <a [routerLink]="['/']" class="btn btn-default"> Cancel</a> <button type="submit" class="btn btn-primary" [disabled]="userForm.invalid">Login</button> </div> </div> </fieldset> </form> </div> </div>
Ora il nostro modulo dovrebbe funzionare! Proviamolo!
Vai a http: // localhost: 4200 / login e prova ad accedere con credenziali valide:
Se inserisci una password sbagliata, vedrai un messaggio di errore:
Angular: Installare il package JWT
Il package angular2-jwt è una libreria di supporto che ci consente di gestire il JWT nelle nostre applicazioni Angular. Questo consente di:
- Allegare un JWT come intestazione di autorizzazione quando effettui richieste HTTP.
- Decodificare un JWT.
- Controllare la data di scadenza di un JWT.
- Consentire agli utenti di accedere a un percorso, in base allo stato JWT.
Nota: anche se il pacchetto si chiama Angular 2 JWT, funziona benissimo con le versioni successive del framework.
Quindi installiamolo, da terminale e dalla root principale del progetto digitate:
npm install @auth0/angular-jwt
Se vedi questi errori:
UNMET PEER DEPENDENCY
Non preoccuparti, il pacchetto funzionerà comunque bene.
Quindi, apri app.module.ts e trova:
@NgModule({
Aggiungi sopra:
import { JwtModule } from '@auth0/angular-jwt'; export function tokenGetter(): any { return localStorage.getItem('token'); }
Ultimo passaggio, trova:
ReactiveFormsModule,
Aggiungi sotto:
JwtModule.forRoot({ config: { tokenGetter: (tokenGetter), allowedDomains: ['localhost:4200'], disallowedRoutes: ['localhost:4200/admin/'] } })
Abbiamo appena importato il modulo JwtModule ed aggiunto negli imports . Dobbiamo anche inserire nella allowed tutti i domini a cui vogliamo fare richieste specificando un allowedDomains.
Angular:Proteggi i nostri percorsi utilizzando Route Guards
Il metodo Route Guards (Navigation Guards ) in Angular, ci consente di proteggere le rotte delle nostre applicazioni, vediamo quali sono:
- CanActivate : rotta che deve essere attivata o meno.
- CanActivateChild : un percorso figlio che deve essere attivato o meno.
- CanDeactivate : un percorso che deve essere disattivato o meno.
- CanLoad : dice ad Angular se un modulo può essere caricato o meno.
- Resolve: assicurati che i dati del percorso vengano caricati prima che venga attivato un percorso.
Documentazione: puoi saperne di più sulle Route guards leggendo la documentazione ufficiale .
In Laravel o in Node.js possono essere definiti una sorta di middleware .
Vediamo come utilizzarle, creaiamo un nuovo servizio AuthGuard , che viene utilizzato per verificare se l’utente può accedere o meno al percorso.
Da terminale esegui questo comando per generare il servizio:
ng g s services/auth-guard
Ecco il contenuto di auth-guard.service.ts :
auth-guard.service.ts
import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuardService { constructor(private auth: AuthService, private router: Router) { } canActivate(): any { if (!this.auth.loggedIn()) { this.router.navigate(['/login']); return false; } return true; } }
Dai un’occhiata alla funzione canActivate :
canActivate(): any { if (!this.auth.loggedIn()) { this.router.navigate(['/login']); return false; } return true; }
Utilizziamo la funzione di accesso di AuthService per verificare se l’utente ha effettuato l’accesso. Se l’utente non è connesso, reindirizziamo l’utente alla pagina di accesso e restituiamo false . Altrimenti, restituiamo true ; il che significa che il percorso è accessibile dall’utente.
Successivamente, dovremo creare la funzione di login di AuthService .
Apri services/auth.service.ts e aggiungi:
loggedIn(): any { return !this.jwtHelper.isTokenExpired(); }
Come avrai intuito, il metodo jwtHelper.isTokenExpired () proviene dalla libreria angular-jwt !
Assicurati di importarlo:
import { JwtHelperService } from '@auth0/angular-jwt';
e iniettalo al nostro costruttore:
constructor(private http: HttpClient, public jwtHelper: JwtHelperService) {}
Questo metodo di supporto viene utilizzato per verificare se è presente un JWT non scaduto nella memoria locale. In poche parole, se c’è un token e non è ancora scaduto, questo ritorna false
(loggato). Se non è presente alcun token o il token è scaduto, il metodo restituisce true
(non connesso).
Solo un promemoria, nella funzione di accesso , quando riceviamo un token, aggiungiamo il token nella memoria locale utilizzando:
localStorage.setItem('token', this.token);
Quindi il nome del token è token
, e deve essere lo stesso di quello utilizzato nel metodo tokenGetter
, in app.module.ts :
export function tokenGetter() { return localStorage.getItem('token'); }
La nostra prima Route guard quindi è pronta !
Per applicarla apri app.routing-module.ts e trova:
{ path: 'admin', component: DashboardComponent, children: adminRoutes},
modifica in :
{ path: 'admin', component: DashboardComponent, children: adminRoutes, canActivate: [AuthGuardService]},
assicurati di importare:
import {AuthGuardService} from './services/auth-guard.service';
Ora non possiamo accedere alla rotta amministrativa a meno che non siamo loggati!
Visita http:// localhost:4200/admin e provalo!
Angular: Creare il metodo per il logout
Non abbiamo ancora realizzato un modo per effettuare il logout, allora implementiamolo.
Vai alla directory principale dell’app ed esegui questo comando:
ng g c components/logout
Apri app-routing.modules.ts e aggiungi un nuovo percorso:
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'home', component: HomeComponent}, {path: 'about', component: AboutComponent}, {path: 'contact', component: ContactComponent}, { path: 'admin', component: DashboardComponent, children: adminRoutes, canActivate: [AuthGuardService]}, { path: 'register', component: RegisterComponent}, { path: 'login', component: LoginComponent}, { path: 'logout', component: LogoutComponent}, ];
ricordati di importare il componente:
import {LogoutComponent} from './components/logout/logout.component';
Ora, ovviamente, dobbiamo creare un nuovo metodo di logout in AuthService :
auth.service.ts
logout(): void { this.token = null; localStorage.removeItem('token'); console.log('you are logged out!'); this.router.navigate(['/']); }
iniettiamo il router nel costruttore:
constructor(private http: HttpClient, public jwtHelper: JwtHelperService, private router: Router) { }
Come puoi vedere, iniziamo impostando il token su null e rimuovendo il token dalla memoria locale. Successivamente, reindirizziamo gli utenti alla home page.
Infine, modifichiamo il componente components/logout . Quando il componente di logout viene inizializzato, dovrebbe chiamare il metodo di logout di AuthServer :
import { Component, OnInit } from '@angular/core'; import {AuthService} from '../../services/auth.service'; @Component({ selector: 'app-logout', template: ` <p> logout works! </p> `, styleUrls: ['./logout.component.css'] }) export class LogoutComponent implements OnInit { constructor(private authService: AuthService) { } ngOnInit(): void { this.authService.logout(); } }
ora provalo su http:// localhost:4200/logout, se tutto va bene verrai disconnesso!