راه اندازی سرویس احراز هویت در angular
بعد از ایجاد پروژه انگولار گام اول کار ما ایجاد سرویس احراز هویت هست . ما در اینجا از قبلا api هامونو با فریم ورک لاراول درست کردیم و از سیستم otp استفاده میکنیم .
در ابتدا سرویس ها و کامپوننت هایی رو ا دستورهای زیر درست میکنیم :
ng g s _services/auth ng g s _services/token-storage ng g s _services/user ng g c login ng g c otpverify ng g c home ng g c profile ng g c board-admin ng g c board-user
به فایل app.module.ts این دو مورد رو اضافه میکنیم :
import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http';
توجه داشته باشید سرویس ها و کامپوننت هایی که تو مرحله قبل ایجاد کردیم حتما در این فایل اضافه شده باشند .
خب اول میریم سراغ فایل_services/auth.service.ts .این سرویس درخواستهای ورود به سیستم بکاند ارسال میکند.
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; const AUTH_API = 'http://localhost:8000/api/auth/'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private http: HttpClient) { } login(phone_number: string): Observable<any> { return this.http.post(AUTH_API + 'login', { phone_number }, httpOptions); } verify(phone_number: stringcode:string): Observable<any> { return this.http.post(AUTH_API + 'verifyotp', { phone_number,code }, httpOptions); } }
و بعد سراغ فایل_services/token-storage.service.ts
TokenStorageService برای مدیریت اطلاعات کاربر در فضای ذخیرهسازی جلسه مرورگره. برای خروج، ما فقط باید این Session Storage را پاک کنیم.
import { Injectable } from '@angular/core'; const TOKEN_KEY = 'auth-token'; const USER_KEY = 'auth-user'; @Injectable({ providedIn: 'root' }) export class TokenStorageService { constructor() { } signOut(): void { window.sessionStorage.clear(); } public saveToken(token: string): void { window.sessionStorage.removeItem(TOKEN_KEY); window.sessionStorage.setItem(TOKEN_KEY, token); } public getToken(): string | null { return window.sessionStorage.getItem(TOKEN_KEY); } public saveUser(user: any): void { window.sessionStorage.removeItem(USER_KEY); window.sessionStorage.setItem(USER_KEY, JSON.stringify(user)); } public getUser(): any { const user = window.sessionStorage.getItem(USER_KEY); if (user) { return JSON.parse(user); } return {}; } }
خب مرحله بعد مربوط به فایل _services/user.service.ts هست .این سرویس روش هایی را برای دسترسی به منابع عمومی و حفاظت شده ارائه می دهد.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; const API_URL = 'http://localhost:8000/api/test/'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) { } getPublicContent(): Observable<any> { return this.http.get(API_URL + 'all', { responseType: 'text' }); } getUserBoard(): Observable<any> { return this.http.get(API_URL + 'user', { responseType: 'text' }); } getAdminBoard(): Observable<any> { return this.http.get(API_URL + 'admin', { responseType: 'text' }); } }
import { HTTP_INTERCEPTORS, HttpEvent } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { TokenStorageService } from '../_services/token-storage.service'; import { Observable } from 'rxjs'; const TOKEN_HEADER_KEY = 'x-access-token'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private token: TokenStorageService) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let authReq = req; const token = this.token.getToken(); if (token != null) { authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) }); } return next.handle(authReq); } } export const authInterceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ];
خب در مرحله بعد کامپوننت هارو اصلاح میکنیم :
<div class="col-md-12"> <div class="card card-container"> <form *ngIf="!isLoggedIn" name="form" (ngSubmit)="f.form.valid && onSubmit()" #f="ngForm" novalidate > <div class="form-group"> <label for="phone_number">phone_number</label> <input type="text" class="form-control" name="phone_number" [(ngModel)]="form.phone_number" required #phone_number="ngModel" min="10" max="13" /> <div class="alert alert-danger" role="alert" *ngIf="phone_number.errors && f.submitted" > phonenumber is required! </div> </div> <div class="form-group"> <button class="btn btn-primary btn-block"> Login </button> </div> <div class="form-group"> <div class="alert alert-danger" role="alert" *ngIf="f.submitted && isLoginFailed" > Login failed: {{ errorMessage }} </div> </div> </form> <div class="alert alert-success" *ngIf="isLoggedIn"> Logged in as {{ roles }}. </div> </div> </div>
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../_services/auth.service'; import { TokenStorageService } from '../_services/token-storage.service'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) export class LoginComponent implements OnInit { form: any = { phone_number: null, }; isLoggedIn = false; isLoginFailed = false; errorMessage = ''; roles: string[] = []; constructor(private authService: AuthService, private tokenStorage: TokenStorageService,private http:HttpClient ,private router: Router ) { } ngOnInit(): void { if (this.tokenStorage.getToken()) { this.isLoggedIn = true; this.roles = this.tokenStorage.getUser().roles; } } onSubmit(): void { const { phone_number} = this.form; this.authService.login(phone_number,ip,userAgent).subscribe( data => { this.isLoginFailed = true; }, err => { this.errorMessage = err.error.message; this.isLoginFailed = true; } ); } }
برای صفحه وارد کردن کد otp ما از پکیج ng-otp-input استفاده میکنیم :
<div class="col-md-12"> <div class="card card-container"> <form *ngIf="!isLoggedIn" name="form" (ngSubmit)="f.form.valid && onSubmit()" #f="ngForm" novalidate > <ng-otp-input #ngOtpInput (onInputChange)="onOtpChange($event)" *ngIf="showOtpComponent" [config]="config" ></ng-otp-input> <div class="form-group"> <button class="btn btn-primary btn-block"> Login </button> </div> <div class="form-group"> <div class="alert alert-danger" role="alert" *ngIf="f.submitted && isLoginFailed" > Login failed: {{ errorMessage }} </div> </div> </form> <div class="alert alert-success" *ngIf="isLoggedIn"> Logged in as {{ roles }}. </div> </div> </div>
import { Component, OnInit , ViewChild } from '@angular/core'; import { AuthService } from '../_services/auth.service'; import { TokenStorageService } from '../_services/token-storage.service'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; @Component({ selector: 'app-otpveirfy', templateUrl: './otpverify.component.html', styleUrls: ['./otpverify.component.scss'] }) export class OtpverifyComponent implements OnInit { form: any = { phone_number: null, }; isLoggedIn = false; isLoginFailed = false; errorMessage = ''; roles: string[] = []; ipAddress = ''; otp: string =''; showOtpComponent = true; @ViewChild("ngOtpInput", { static: false }) ngOtpInput: any; config = { allowNumbersOnly: true, length: 5, isPasswordInput: false, disableAutoFocus: false, placeholder: "*", inputStyles: { width: "50px", height: "50px", }, }; constructor( private router: Router, private authService: AuthService, private tokenStorage: TokenStorageService,private http:HttpClient) { } ngOnInit(): void { if (this.tokenStorage.getToken()) { this.isLoggedIn = true; this.roles = this.tokenStorage.getUser().roles; } } onSubmit(): void { const code:any = this.form;; this.authService.verify(phone_number,code).subscribe( data => { console.log('ok'); this.tokenStorage.saveToken(data.accessToken); this.tokenStorage.saveUser(data); this.isLoginFailed = false; this.isLoggedIn = true; this.roles = this.tokenStorage.getUser().roles; }, err => { this.errorMessage = err.error.message; this.isLoginFailed = true; } ); } reloadPage(): void { window.location.reload(); } onOtpChange($event:any){ console.log($event); } }
برای کامپوننت پروفایل که کاربر فعلی را از Storage با استفاده از TokenStorageService دریافت می کند و اطلاعات اونو را نشان میده.
import { Component, OnInit } from '@angular/core'; import { TokenStorageService } from '../_services/token-storage.service'; @Component({ selector: 'app-profile', templateUrl: './profile.component.html', styleUrls: ['./profile.component.css'] }) export class ProfileComponent implements OnInit { currentUser: any; constructor(private token: TokenStorageService) { } ngOnInit(): void { this.currentUser = this.token.getUser(); } } ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ <div class="container" *ngIf="currentUser; else loggedOut"> <header class="jumbotron"> <h3> <strong>{{ currentUser.username }}</strong> Profile </h3> </header> <p> <strong>Token:</strong> {{ currentUser.accessToken.substring(0, 20) }} ... {{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }} </p> <p> <strong>Email:</strong> {{ currentUser.email }} </p> <strong>Roles:</strong> <ul> <li *ngFor="let role of currentUser.roles"> {{ role }} </li> </ul> </div> <ng-template #loggedOut> Please login. </ng-template>
import { Component, OnInit } from '@angular/core'; import { UserService } from '../_services/user.service'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { content?: string; constructor(private userService: UserService) { } ngOnInit(): void { this.userService.getPublicContent().subscribe( data => { this.content = data; }, err => { this.content = JSON.parse(err.error).message; } ); } } ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ <div class="container"> <header class="jumbotron"> <p>{{ content }}</p> </header> </div>
بعضی کامپوننت ها مبتنی بر نقش کاربر هستند مثل کامپوننت های مدیریت . اونارو هم باید به شکل زیر تغییر بدیم مثلا:
board-admin/board-admin.component.ts ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ import { Component, OnInit } from '@angular/core'; import { UserService } from '../_services/user.service'; @Component({ selector: 'app-board-admin', templateUrl: './board-admin.component.html', styleUrls: ['./board-admin.component.css'] }) export class BoardAdminComponent implements OnInit { content?: string; constructor(private userService: UserService) { } ngOnInit(): void { this.userService.getAdminBoard().subscribe( data => { this.content = data; }, err => { this.content = JSON.parse(err.error).message; } ); } } ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ ٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫٫ <div class="container"> <header class="jumbotron"> <p>{{ content }}</p> </header> </div>
وبعد روت هامونو هم اضافه میکنیم :
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { VerifyComponent } from './verify/verify.component'; import { LoginComponent } from './login/login.component'; import { HomeComponent } from './home/home.component'; import { ProfileComponent } from './profile/profile.component'; import { BoardUserComponent } from './board-user/board-user.component'; import { BoardAdminComponent } from './board-admin/board-admin.component'; const routes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'login', component: LoginComponent }, { path: 'verify', component: VerifyComponent }, { path: 'profile', component: ProfileComponent }, { path: 'user', component: BoardUserComponent }, { path: 'admin', component: BoardAdminComponent }, { path: '', redirectTo: 'home', pathMatch: 'full' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
import { Component, OnInit } from '@angular/core'; import { TokenStorageService } from './_services/token-storage.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { private roles: string[] = []; isLoggedIn = false; showAdminBoard = false; username?: string; constructor(private tokenStorageService: TokenStorageService) { } ngOnInit(): void { this.isLoggedIn = !!this.tokenStorageService.getToken(); if (this.isLoggedIn) { const user = this.tokenStorageService.getUser(); this.roles = user.roles; this.showAdminBoard = this.roles.includes('ROLE_ADMIN'); this.username = user.username; } } logout(): void { this.tokenStorageService.signOut(); window.location.reload(); } }
<div id="app"> <nav class="navbar navbar-expand navbar-dark bg-dark"> <a href="#" class="navbar-brand">bezKoder</a> <ul class="navbar-nav mr-auto" routerLinkActive="active"> <li class="nav-item"> <a href="/home" class="nav-link" routerLink="home">Home </a> </li> <li class="nav-item" *ngIf="showAdminBoard"> <a href="/admin" class="nav-link" routerLink="admin">Admin Board</a> </li> <li class="nav-item"> <a href="/user" class="nav-link" *ngIf="isLoggedIn" routerLink="user">User</a> </li> </ul> <ul class="navbar-nav ml-auto" *ngIf="!isLoggedIn"> <li class="nav-item"> <a href="/register" class="nav-link" routerLink="register">Sign Up</a> </li> <li class="nav-item"> <a href="/login" class="nav-link" routerLink="login">Login</a> </li> </ul> <ul class="navbar-nav ml-auto" *ngIf="isLoggedIn"> <li class="nav-item"> <a href="/profile" class="nav-link" routerLink="profile">{{ username }}</a> </li> <li class="nav-item"> <a href class="nav-link" (click)="logout()">LogOut</a> </li> </ul> </nav> <div class="container"> <router-outlet></router-outlet> </div>