Authentication trong Angular là xác thực người dùng, nhằm xác định người đang dùng ứng dụng là ai để cấp quyền truy cập.
Liên quan đến authentication, có một số việc cần làm cơ bản sau đây: Xử lý đăng nhập của user, chức năng thoát, nhận thông tin profile của người dùng, bảo vệ ứng dụng qua các đường route và gọi hàm tại các vị trí cần thiết để kiểm tra.
- 1. Chuẩn bị
- 2. Tạo chức năng đăng nhập
- 3. Code phía server tạo Json Web Token session
- 4. Lưu trữ và sử dụng JWT ở client side
- 5. Chức năng thoát
- 6. Kiểm tra đăng nhập
Để thực hiện Authentication trong Angular, bạn cần biết các loại route guard và cách tạo. Route guard là bảo vệ các route trong ứng dụng. User được phép vào 1 route nào đó hay không tùy thuộc vào sự đánh giá true/false của guard. Trong Angular có các loại route guard là: CanActivate, CanActivateChild, CanDeactivate, CanLoad…
Guard được tạo ra là để bảo vệ các route, do đó khai báo guard ở từng route
1. Chuẩn bị
Đây là bài khá dài và cũng khá công phu cho việc triển khai các chức năng, cho nên chúng ta cần 1 số chuẩn bị trước khi vào chủ đề chính.
a. Chuẩn bị ứng dụng angular phía client
– Tạo ứng dụng :Tạo ứng dụng angular để thực hiện authentication trong dự án. Tên ứng dụng sao cũng được
ng new authen --defaults
– Tạo các component : Tạo các component để thực tập authentication
ng g c home
ng g c downLoad
ng g c dangKy
ng g c dangNhap
ng g c doiPass
– Tạo guard để bảo vệ các route: ng generate guard Baove
– Khai báo config để sử dụng http serviceL
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { importProvidersFrom } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideClientHydration()
, importProvidersFrom(HttpClientModule)
, provideHttpClient(withFetch())
]
};
– Khai báo route cho các component : Mở app.routes.ts khai báo route cho các component
//app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DangKyComponent } from './dang-ky/dang-ky.component';
import { DangNhapComponent } from './dang-nhap/dang-nhap.component';
import { DoiPassComponent } from './doi-pass/doi-pass.component';
import { DownLoadComponent } from './down-load/down-load.component';
import { baoveGuard } from './baove.guard';
export const routes: Routes = [
{ path:'', component:HomeComponent},
{ path:'dangnhap', component:DangNhapComponent},
{ path:'dangky', component:DangKyComponent},
{ path:'doipass', component:DoiPassComponent,
canActivate:[baoveGuard], },
{ path:'download', component:DownLoadComponent,
canActivate:[baoveGuard], },
];
– Tạo layout: Nhúng bootstrap và code tạo layout (có menu, router-outlet
<!--src/index.html-->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- app/app.component.html-->
<div class="container">
<header class="bg-info" style="height: 90px"></header>
<nav>
<nav class="navbar navbar-expand bg-warning">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="#" routerLink="/">Trang chủ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" routerLink="dangnhap">Đăng nhập</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" routerLink="dangky">Đăng ký</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" routerLink="doipass">Đổi pass</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" routerLink="download">Download</a>
</li>
</ul>
</nav>
</nav>
<main class="d-flex" style="min-height: 300px;">
<article class="col-md-9 bg-body-secondary">
<router-outlet></router-outlet>
</article>
<aside class="col-md-3 bg-info-subtle"> </aside>
</main>
</div>
– Tạo service auth: Chạy lệnh ng g s auth để tạo service có tên auth (hoặc tên gì cũng được) . Nơi đây bạn sẽ code các hàm authentication.
b. Chuẩn bị ứng dụng phía server
– Tạo folder bai7_ServerNodeJS
– Chuyển vào folder mới tạo và chạy lệnh npm init và gõ Enter chấp nhận tất cả các thông số mặc định.
– Chạy lệnh cài module express: npm install express
– Tạo file server.js sử dụng module express
const exp = require("express");
const app = exp();
const port = 3000;
app.get("/", (req, res) => {
res.send("<h1>Đây là trang home</h1>");
});
app.listen(port, () =>{
console.log(`Ung dung dang chay voi port ${port}`);
});
– Chạy ứng dụng: node server.js
– Xem trong trình duyệt: http://localhost:3000
Như vậy chúng ta đã chuẩn bị xong 2 ứng dụng. Ứng dụng angular hoạt động như client và ứng dụng NodeJS hoạt động như server phía backend. Dùng cả hai để thực hiện giải pháp authentication trong Angular.
2. Tạo chức năng đăng nhập
Tạo form đăng nhập
Để người dùng đăng nhập, bạn tạo form đăng nhập (trong angular) .Thực hiện như sau
– Import form module
//dang-nhap.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-dang-nhap', standalone: true,
imports: [FormsModule],
templateUrl: './dang-nhap.component.html',
styleUrl: './dang-nhap.component.css'
})
export class DangNhapComponent { }
– Tạo form trong view
<!-- dang-nhap.component.html -->
<form #frm1="ngForm" class="col-10 m-auto p-2 border border-primary"
(ngSubmit)="xulyDN(frm1.value)" >
<h4>THÀNH VIÊN ĐĂNG NHẬP</h4>
<p>Username
<input name="un" ngModel class="form-control border-primary" type="text">
</p>
<p> Password
<input name="pw" ngModel class="form-control border-primary" type="password">
</p>
<p>
<button type="submit" class="btn btn-success">Đăng nhập </button>
</p>
</form>
Xử lý đăng nhập trong form
Import và tạo Router, authService, định nghĩa hàm xulyDN để gọi hàm login trong service
//dang-nhap.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-dang-nhap',
templateUrl: './dang-nhap.component.html',
styleUrls: ['./dang-nhap.component.css']
})
export class DangNhapComponent implements OnInit {
constructor(
private auth:AuthService,
private router: Router
) { }
xulyDN(data:any){
this.auth.login( data.un, data.pw).subscribe( ()=>{
console.log("Đăng nhập thành công");
this.router.navigateByUrl('/');
}
)
}
}
Định nghĩa hàm login trong service để gọi lên server
Trong auth service , sử dụng http service để submit username và password lên cho server kiểm tra.
//auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable({ providedIn: 'root'})
export class AuthService {
constructor( private _http:HttpClient) { }
login(username:string='', password:string=''){
const userInfo = { un:username, pw:password }
const headers = new HttpHeaders().set('Content-Type', 'application/json') ;
return this._http.post('http://localhost:3000/login'
, JSON.stringify(userInfo)
, {headers:headers, responseType: 'text'}
)
}//login
}
3. Code phía server tạo Json Web Token session
Phía server sẽ đón nhận request post chứa thông tin người dùng từ form login gửi lên và kiểm tra thông tin hợp lệ không. Nếu OK thì 1 token sẽ được tạo ra và gửi cho phía client (tức trình duyệt) sau khi tạo để dùng cho liên lạc trong các request sau đó.
Cài SSL
Vào https://kb.firedaemon.com/support/solutions/articles/4000121705-openssl-3-0-and-1-1-1-binary-distributions-for-microsoft-windows để download OpenSSL. Sau đó giải nén file download sẽ có source OpenSSL để dùng (trong folder x64\bin)
Mở command line rồi vào folder x64\bin chạy các lệnh sau để tạo provite key, public key:
– Tạo private key:
openssl genrsa -out private-key.txt
– Tạo public key:
openssl rsa -in private-key.txt -pubout -out public-key.txt
Chép 2 file mới tạo private-key.txt và public-key.txt sang folder b7_ServerNodeJS để dùng
Cài các module cho NodeJS
Cài module cors để cho phép http request cross domain, module moment để trợ giúp tính ngày giờ (để xem session user hết hạn chưa), module node-jsonwebtoken để tạo token gửi về cho client
npm i cors
npm i moment
npm i node-jsonwebtoken
Code xử lý tạo và trả về token
Code trong server.js (phía server) thực hiện các việc sau:
- Tạo path có tên login với method post để đón post request gửi lên từ client.
- Nhận username và password trong body của request rồi gọi hàm check userpass xem có đúng không (trả về true/false). Nếu sai trả về status 401 , nếu OK thì tạo token rồi send lại cho client
- Định nghĩa hàm check userpass để kiểm tra user pass có ok không. Code sau là demo nhẹ cho dễ hiểu, sau này bạn code kết nối vào db thêm nhé
- Định nghĩa hàm lấy thông tin user để lấy đầy đủ thông tin của user theo username.
//server.js
const exp = require("express");
const fs = require('fs');
const bodyParser = require("body-parser");
const jwt = require('jsonwebtoken');
var cors = require('cors')
const app = exp();
const port = 3000;
const PRIVATE_KEY = fs.readFileSync('private-key.txt');
app.use(bodyParser.json());
app.use(cors());
app.get("/", (req, res) => { res.send("<h1>Đây là trang home</h1>");});
app.post('/login', (req, res) => {
const un = req.body.un;
const pw = req.body.pw;
if (checkUserPass(un, pw)) {
const userInfo = getUserInfo(un);
const jwtBearerToken = jwt.sign({}, PRIVATE_KEY, {
algorithm: 'RS256',
expiresIn: 120,
subject: userInfo.id
})
//res.cookie("SESSIONID", jwtBearerToken, {httpOnly:true, secure:false});
res.status(200).json({ idToken: jwtBearerToken, expiresIn: 120 });
}
else res.sendStatus(401); // send status 401 Unauthorized
})
checkUserPass = (un, pw) => {
if (un=='aa' && pw=='123') { return true}
if (un=='bb' && pw=='321') { return true}
return false;
}
getUserInfo = (username) =>{
if (username=='aa') return { "id":"1", hoten:"Nguyễn Văn Tèo" }
if (username=='bb') return { "id":"2", hoten:"Nguyễn Thị Lượm" }
return {"id":"-1", "hoten":""}
}
app.listen(port, () =>{
console.log(`Ung dung dang chay voi port ${port}`);
});
4. Lưu trữ và sử dụng JWT ở client side
Token có thể lưu trong cookie hoặc localstorage để dùng sau (cho các chức năng cần check đăng nhập)
– Trong project angular, cài đặt module moment : npm i moment
– Code trong component đăng nhập
//dang-nhap.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../auth.service';
import moment from 'moment';
import { Router } from '@angular/router';
@Component({
selector: 'app-dang-nhap', standalone: true,
imports: [FormsModule],
templateUrl: './dang-nhap.component.html',
styleUrl: './dang-nhap.component.css'
})
export class DangNhapComponent {
constructor(
private auth:AuthService,
private router: Router) { }
xulyDN(data:any){
console.log(data, data.un , data.pw);
this.auth.login( data.un, data.pw).subscribe(
res =>{
var d = JSON.parse(res);
console.log("Đăng nhập thành công ", res);
const expiresAt = moment().add(d.expiresIn,'second');
localStorage.setItem('id_token', d.idToken);
localStorage.setItem("expires_at",
JSON.stringify(expiresAt.valueOf()) );
this.router.navigateByUrl('/');
},
error => {
console.log('oops', error);
this.router.navigateByUrl('/dangnhap');
}
)
} //xulyDN
}
5. Chức năng thoát
Đơn giản là xóa tất cả các biến đã lưu trong local storage lúc đăng nhập thành công. Thực bằng cách tạo link thoát và gọi hàm thoát khi user click.
Định nghĩa hàm thoát trong service
//auth.service.ts
thoat() {
localStorage.removeItem("id_token");
localStorage.removeItem("expires_at");
localStorage.removeItem("username");
}
Tạo link thoát
Đặt ở đâu tùy bạn, code dưới đây định nghĩa trong app.component.html
<aside class="col-md-3 bg-info-subtle">
<a href="#" (click)="thoat()">Thoát</a>
</aside>
Trong component app, code gọi hàm thoat trong service auth
//app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { RouterLink } from '@angular/router';
import { AuthService } from './auth.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-root', standalone: true,
imports: [RouterOutlet, RouterLink , CommonModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'Authentication trong Angular';
constructor( private auth:AuthService){}
thoat(){ this.auth.thoat(); }
}
6. Kiểm tra đăng nhập
Để kiểm tra user đã đăng nhập chưa, bạn định nghĩa hàm trong service để kiểm tra token quá hạn chưa, nếu chưa thì trả về true còn quá hạn thì trả về false
//auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import moment from 'moment';
import { DOCUMENT } from '@angular/common';
import { Inject } from '@angular/core';
@Injectable({ providedIn: 'root'})
export class AuthService {
constructor(
private _http:HttpClient ,
@Inject(DOCUMENT) private document: Document
) { }
login(username:string='', password:string=''){
const userInfo = { un:username, pw:password }
const headers = new HttpHeaders().set('Content-Type', 'application/json') ;
return this._http.post('http://localhost:3000/login'
, JSON.stringify(userInfo)
, {headers:headers, responseType: 'text'}
)
}//login
daDangNhap() {
let localStorage = this.document.defaultView?.localStorage
if (!localStorage) return false;
const str = localStorage.getItem("expires_at") || "";
if (str=="") return false; //chưa dn
const expiresAt = JSON.parse(str);
return moment().isBefore(moment(expiresAt));
} //daDangNhap
thoat() {
localStorage.removeItem("id_token");
localStorage.removeItem("expires_at");
localStorage.removeItem("username");
}
}
Định nghĩa hàm daDangNhap trong app component
//app.component.ts
...
daDangNhap() { return this.auth.daDangNhap()}
Gọi hàm daDangNhap trong view:
<!-- app.component.html-->
<aside class="col-md-3 bg-info-subtle">
<a href="#" (click)="thoat()">Thoát</a>
<p> Tình trạng đăng nhập: {{ daDangNhap() }}</p>
</aside>
Test : thử thoát và đăng nhập, sẽ thấy giá trị true/false hiện ra
Ẩn hiện tag theo tình trạng đăng nhập
<!-- app.component.html-->
<li *ngIf="daDangNhap()" class="nav-item">
<a class="nav-link" routerLink="doipass" href="#"> Đổi pass</a>
</li>
Code trong guard bảo vệ route
//baove.guard.ts
import { CanActivateFn } from '@angular/router';
import { Router } from '@angular/router';
import moment from 'moment';
export const baoveGuard: CanActivateFn = (route, state) => {
const str = localStorage.getItem("expires_at") || "";
if (str=="") return false; //chưa dn
const expiresAt = JSON.parse(str);
const daDangNhap = moment().isBefore(moment(expiresAt));
return daDangNhap
};
Test thử : khi chưa đăng nhập, nhắp vào link Download trên menu bị chuyển sang trang đăng nhập, nếu đã đăng nhập thì sẽ xem được view download.
Mời bạn đọc các link sau để tham khảo thêm:
- https://blog.angular-university.io/angular-jwt-authentication/
- https://www.scottbrady91.com/openssl/creating-rsa-keys-using-openssl
- https://www.webdevsplanet.com/post/how-to-generate-rsa-private-and-public-keys
- https://longnv.name.vn/lap-trinh-angular-framework/su-dung-http-service-trong-angular
Mời bạn thực tập triển khai thêm
- Tạo nội dung cho view download
- Chức năng đổi pass
- Cải thiện đăng nhập phía server (hàm checkUserPass) kết nối vào database để check
- Cải thiện đăng nhập phía server (hàm getUseInfo) kết nối vào database để lấy dữ liệu
- Cải thiện đăng nhập phía client: bổ sung thêm để quay lại trang cũ (hiện chỉ quay lại đúng trang chủ)