Hệ thống không sử dụng password của Slack mà thay vào đó sử dụng magic link thực sự là 1 cách thay thế tuyệt vời cho hệ thống xác thực cổ điển bằng email/password trên rất nhiều phương diện. Kể cả khi bạn không điều khiển các mức độ bảo mật của hệ mã hóa password thì nhà cung cấp mailbox cho người dùng cũng đã đủ an toàn và thực tế thì họ trả về cho bạn 1 token là đủ để xác thực. Dưới đây là 1 cách thức đơn giản xác thực bằng OAuth (giống như Facebook) - cách này sẽ không cung cấp bất kì thông tin gì về người dùng.

https://cdn-images-1.medium.com/max/2000/1*VIPWScFOxe2mdNaMQrqEmg.png
difference between oAuth and magic link

 

Cách thực thi

Với sơ đồ trên thì ta chỉ cần thực thi 2 endpoint. Một endpoint để submit request với 1 key xác thực và endpoint còn lại sẽ nhận thông tin đã xác thực.

const express = require('express');
const server = express();

server.use(require('body-parser').json());
server.use(require('./middleware/authentication').authenticated);

// The access point to authenticate
server.post('/api/accounts/login', require('./controller/accounts-login'));

// The access point to verify that we are authenticated
server.get('/api/accounts/me', require('./controller/accounts-me'));

module.exports = server;

Với endpoint xác thực, ta cần 3 thứ: quyền truy cập vào database để duy trì tài khoản, sinh token và sinh mail. Để sinh ra token, ta sử dụng phương pháp JSON Web Token. Ta sẽ có 1 chút thuận lợi ở điểm có thể nhúng 1 số nội dung vào token và có thể đảm bảo được rằng nó không bị chỉnh sửa.

const jwt = require('jsonwebtoken');
const config = require('../config');

const generate = (account) =>
  jwt.sign({ id: account.id }, config.jwt.secret);

module.exports = { generate };

Giờ thì ta sẽ sinh 1 link chứa token trong query và gửi link đó đến người dùng bằng email.

MailService.create({
  to: body.value.email,
  subject: 'Magic link 🎩',
  html: `
    <h1>Magic link 🎩 !</h1>
    <a href="${config.host}/api/accounts/me?token=${encodeURIComponent(token)}">
      YOUHOU
    </a>
  `,
  text: `Magic link: ${config.host}/api/accounts/me?token=${encodeURIComponent(token)}`,
});

Khi người dùng click vào link, ta cần truyền token vào như là tham số truy xuất, giải mã nội dung và đưa nó vào request. Với mục đích đó, ta cần phải sử dụng 1 middleware Express tên là express-jwt.

const jwt = require('express-jwt');
const config = require('../config');

const authenticated = jwt({
  secret: config.jwt.secret,
  credentialsRequired: false,
  getToken: (req) => {
    if (req.query) return req.query.token;
    return null;
  },
});

module.exports = { authenticated };

Bây giờ, khi người dùng ấn vào link, ta sẽ có thông tin của họ và có thể sử dụng chúng.

module.exports = (req, res) => {
  if (req.user) {
    // here you can fetch the user from the database
    return res.json(req.user);
  }
  return res
    .status(401)
    .send({ message: 'not authenticated' });
};

Tới đây thì chu kì trên hình đầu bài viết đã kết thúc. Lúc này người dùng đã được xác thực chỉ bằng email của họ.

 

Vấn đề bảo mật thì sao?

Đầu tiên, ta cần nhận ra rằng mailbox của người dùng có thể bị xâm phạm. Nếu có ai đó chặn mail chứa token, họ có thể truy cập vào tài khoản của người dùng chừng nào mà token vẫn còn hiệu lực. Tuy nhiên có 1 vài cách giúp tránh được việc này.

Cách đầu tiên là sử dụng 1 blacklist chứa các token ta muốn bỏ đi. Khá phiền và hơi phản lại nguyên lý tạo token.

Thế sao không thêm revision index vào token để lưu trữ trong database? Với cách này, sẽ chỉ token với revision cuối cùng được sử dụng để login. Giải pháp này lại đi ngược lại với nguyên lý tạo token do mỗi lần request ta lại phải vào database để kiểm tra.

Giải pháp tốt nhất đó chính là set cho token được gửi bằng mail 1 thời gian sống ngắ, khoảng 1 giờ. Lúc đó thì request đầu tiên người dùng tạo ra và gửi đến server sẽ được kèm với token có thời gian sống lâu hơn.

Cũng là 1 giải pháp rất ổn khác, đó chính là chia token thành 2 phần, gửi phần thứ nhất cho người dùng bằng mail và trả phần còn lại cho ứng dụng thông qua response trả về từ server. Lúc này mobile hay trình duyệt sẽ dịch lại token và xác thực.

Đây là source code ví dụ: https://github.com/jdrouet/magic-link-example

 

Bài viết được dịch từ: https://medium.com/one-more-thing-studio/how-to-make-magic-links-with-node-1d164c036e29