GraphQL - Xác thực khách hàng
Xác thực là quá trình hoặc hành động xác minh danh tính của người dùng hoặc một quá trình. Điều quan trọng là ứng dụng phải xác thực người dùng để đảm bảo rằng dữ liệu không có sẵn cho người dùng ẩn danh. Trong phần này, chúng ta sẽ tìm hiểu cách xác thực một máy khách GraphQL.
JWT Express
Trong ví dụ này, chúng tôi sẽ sử dụng jQuery để tạo một ứng dụng khách. Để xác thực yêu cầu, chúng tôi sẽ sử dụng express-jwt ở phía máy chủ.
Mô-đun express-jwt là một phần mềm trung gian cho phép bạn xác thực các yêu cầu HTTP bằng cách sử dụng mã thông báo JWT. Mã thông báo web JSON (JWT) là một chuỗi dài xác định người dùng đã đăng nhập.
Khi người dùng đăng nhập thành công, máy chủ sẽ tạo mã thông báo JWT. Mã thông báo này xác định rõ ràng một bản ghi. Nói cách khác, mã thông báo là đại diện cho danh tính của người dùng. Vì vậy, lần sau, khi khách hàng đến máy chủ, nó phải xuất trình mã thông báo này để nhận được tài nguyên cần thiết. Ứng dụng khách có thể là ứng dụng di động hoặc ứng dụng web.
Hình minh họa
Chúng ta sẽ thực hiện theo một quy trình từng bước để hiểu được minh họa này.
Thiết lập máy chủ
Sau đây là các bước để thiết lập máy chủ -
Bước 1 - Tải xuống và cài đặt các phụ thuộc bắt buộc cho dự án
Tạo một thư mục auth-server-app. Thay đổi thư mục của bạn thành auth-server-app từ thiết bị đầu cuối. Làm theo các bước từ 3 đến 5 được giải thích trong chương Thiết lập Môi trường.
Bước 2 - Tạo một lược đồ
Thêm vào schema.graphql tập tin trong thư mục dự án auth-server-app và thêm mã sau:type Query
{
greetingWithAuth:String
}
Bước 3 - Thêm trình phân giải
Tạo một tệp resolvers.js trong thư mục dự án và thêm mã sau:
Trình phân giải sẽ xác minh xem đối tượng người dùng đã xác thực có sẵn trong đối tượng ngữ cảnh của GraphQL hay không. Nó sẽ đưa ra một ngoại lệ nếu người dùng đã xác thực không có sẵn.
const db = require('./db')
const Query = {
greetingWithAuth:(root,args,context,info) => {
//check if the context.user is null
if (!context.user) {
throw new Error('Unauthorized');
}
return "Hello from TutorialsPoint, welcome back : "+context.user.firstName;
}
}
module.exports = {Query}
Bước 4 - Tạo tệp Server.js
Phần mềm trung gian xác thực xác thực người gọi bằng Mã thông báo web JSON. URL để xác thực là http://localhost:9000/login.
Đây là một hoạt động đăng bài. Người dùng phải gửi email và mật khẩu sẽ được xác thực từ chương trình phụ trợ. Nếu mã thông báo hợp lệ được tạo bằng phương thức jwt.sign, khách hàng sẽ phải gửi mã này trong tiêu đề cho các yêu cầu tiếp theo.
Nếu mã thông báo hợp lệ, req.user sẽ được đặt với đối tượng JSON được giải mã để được phần mềm trung gian sau này sử dụng để ủy quyền và kiểm soát truy cập.
Đoạn mã sau sử dụng hai mô-đun - jsonwebtoken và express-jwt để xác thực các yêu cầu -
Khi người dùng nhấp vào greet, một yêu cầu cho tuyến đường / graphql được đưa ra. Nếu người dùng chưa được xác thực, anh ta sẽ được nhắc tự xác thực.
Người dùng được hiển thị với một biểu mẫu chấp nhận id email và mật khẩu. Trong ví dụ của chúng tôi, / tuyến đường đăng nhập chịu trách nhiệm xác thực người dùng.
Tuyến / đăng nhập xác minh xem có khớp với thông tin đăng nhập do người dùng cung cấp hay không.
Nếu thông tin xác thực không hợp lệ, ngoại lệ HTTP 401 sẽ được trả lại cho người dùng.
Nếu thông tin xác thực hợp lệ, mã thông báo sẽ được máy chủ tạo. Mã thông báo này được gửi như một phần phản hồi cho người dùng. Điều này được thực hiện bởi hàm jwt.sign.
const expressJwt = require('express-jwt');
const jwt = require('jsonwebtoken');
//private key
const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64');
app.post('/login', (req, res) => {
const {email, password} = req.body;
//check database
const user = db.students.list().find((user) => user.email === email);
if (!(user && user.password === password)) {
res.sendStatus(401);
return;
}
//generate a token based on private key, token doesn't have an expiry
const token = jwt.sign({sub: user.id}, jwtSecret);
res.send({token});
});
Đối với mọi yêu cầu, hàm app.use () sẽ được gọi. Điều này lần lượt sẽ gọi phần mềm trung gian expressJWT. Phần mềm trung gian này sẽ giải mã Mã thông báo web JSON. Id người dùng được lưu trữ trong mã thông báo sẽ được truy xuất và lưu trữ dưới dạng người dùng thuộc tính trong đối tượng yêu cầu.
//decodes the JWT and stores in request object
app.use(expressJwt({
secret: jwtSecret,
credentialsRequired: false
}));
Để cung cấp thuộc tính người dùng trong ngữ cảnh GraphQL, thuộc tính này được gán cho context đối tượng như hình dưới đây -
//Make req.user available to GraphQL context
app.use('/graphql', graphqlExpress((req) => ({
schema,
context: {user: req.user &&apm; db.students.get(req.user.sub)}
})));
Tạo nên server.js trong đường dẫn thư mục hiện tại. Tệp server.js hoàn chỉnh như sau:
const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');
const expressJwt = require('express-jwt'); //auth
const jwt = require('jsonwebtoken'); //auth
const db = require('./db');
var port = process.env.PORT || 9000
const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64');
const app = express();
const fs = require('fs')
const typeDefs = fs.readFileSync('./schema.graphql',{encoding:'utf-8'})
const resolvers = require('./resolvers')
const {makeExecutableSchema} = require('graphql-tools')
const schema = makeExecutableSchema({typeDefs, resolvers})
app.use(cors(), bodyParser.json(), expressJwt({
secret: jwtSecret,
credentialsRequired: false
}));
const {graphiqlExpress,graphqlExpress} = require('apollo-server-express')
app.use('/graphql', graphqlExpress((req) => ({
schema,
context: {user: req.user && db.students.get(req.user.sub)}
})));
app.use('/graphiql',graphiqlExpress({endpointURL:'/graphql'}))
//authenticate students
app.post('/login', (req, res) => {
const email = req.body.email;
const password = req.body.password;
const user = db.students.list().find((user) => user.email === email);
if (!(user && user.password === password)) {
res.sendStatus(401);
return;
}
const token = jwt.sign({sub: user.id}, jwtSecret);
res.send({token});
});
app.listen(port, () => console.info(`Server started on port ${port}`));
Bước 5 - Chạy ứng dụng
Thực hiện lệnh npm start trong terminal. Máy chủ sẽ hoạt động trên cổng 9000. Ở đây, chúng tôi sử dụng GraphiQL làm máy khách để kiểm tra ứng dụng.
Mở trình duyệt và nhập URL http://localhost:9000/graphiql. Nhập truy vấn sau vào trình soạn thảo -
{
greetingWithAuth
}
Trong câu trả lời dưới đây, chúng tôi đã gặp lỗi vì chúng tôi không phải là người dùng được xác thực.
{
"data": {
"greetingWithAuth": null
},
"errors": [
{
"message": "Unauthorized",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"greetingWithAuth"
]
}
]
}
Trong phần tiếp theo, chúng ta hãy tạo một ứng dụng khách để xác thực.
Thiết lập ứng dụng khách JQuery
Trong ứng dụng khách, một nút chào được cung cấp sẽ gọi lược đồ greetingWithAuth. Nếu bạn nhấp vào nút mà không đăng nhập, nó sẽ cung cấp cho bạn thông báo lỗi như bên dưới:
Khi bạn đăng nhập với một người dùng có sẵn trong cơ sở dữ liệu, màn hình sau sẽ xuất hiện:
Truy cập vào greeting, trước tiên chúng ta cần truy cập vào URL http://localhost:9000/login lộ trình như bên dưới.
Phản hồi sẽ chứa mã thông báo được tạo từ máy chủ.
$.ajax({
url:"http://localhost:9000/login",
contentType:"application/json",
type:"POST",
data:JSON.stringify({email,password}),
success:function(response) {
loginToken = response.token;
$('#authStatus')
.html("authenticated successfully")
.css({"color":"green",'font-weight':'bold'});
$("#greetingDiv").html('').css({'color':''});
},
error:(xhr,err) => alert('error')
})
Sau khi đăng nhập thành công, chúng ta có thể truy cập vào greetingWithAuth schema như đưa ra dưới đây. Cần phải có Người cấp quyền cho tất cả các yêu cầu tiếp theo với mã thông báo mang.
{
url: "http://localhost:9000/graphql",
contentType: "application/json",
headers: {"Authorization": 'bearer '+loginToken}, type:'POST',
data: JSON.stringify({
query:`{greetingWithAuth}`
}
Sau đây là mã cho index.html -
<!DOCTYPE html>
<html>
<head>
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
let loginToken = "";
$("#btnGreet").click(function() {
$.ajax({url: "http://localhost:9000/graphql",
contentType: "application/json",
headers: {"Authorization": 'bearer '+loginToken},
type:'POST',
data: JSON.stringify({
query:`{greetingWithAuth}` }),
success: function(result) {
$("#greetingDiv").html("<h1>"+result.data.greetingWithAuth+"</h1>")
},
error:function(jQxhr,error) {
if(jQxhr.status == 401) {
$("#greetingDiv").html('please authenticate first!!')
.css({"color":"red",'font-weight':'bold'})
return;
}
$("#greetingDiv").html('error').css("color","red");
}
});
});
$('#btnAuthenticate').click(function() {
var email = $("#txtEmail").val();
var password = $("#txtPwd").val();
if(email && password) {
$.ajax({
url:"http://localhost:9000/login",
contentType:"application/json",
type:"POST",
data:JSON.stringify({email,password}),
success:function(response) {
loginToken = response.token;
$('#authStatus')
.html("authenticated successfully")
.css({"color":"green",'font-weight':'bold'});
$("#greetingDiv").html('').css({'color':''});
},
error:(xhr,err) => alert('error')
})
}else alert("email and pwd empty")
})
});
</script>
</head>
<body>
<h1> GraphQL Authentication </h1>
<hr/>
<section>
<button id = "btnGreet">Greet</button>
<br/> <br/>
<div id = "greetingDiv"></div>
</section>
<br/> <br/> <br/>
<hr/>
<section id = "LoginSection">
<header>
<h2>*Login first to access greeting </h2>
</header>
<input type = "text" value = "[email protected]" placeholder = "enter email" id = "txtEmail"/>
<br/>
<input type = "password" value = "pass123" placeholder = "enter password" id = "txtPwd"/>
<br/>
<input type = "button" id = "btnAuthenticate" value = "Login"/>
<p id = "authStatus"></p>
</section>
</body>
</html>