Tìm hiểu về Passport.js: Các bước để xác thực tài khoản



  • Passport.js một trong những module phổ biến nhất của Nodejs hỗ trợ bạn authentication . Nó được thiết kế là một middleware hết sức linh hoạt cho bạn khả năng tùy biến cao với rất nhiều các kịch bản authentication: bạn có thể sử dụng Twitter, Facebook, Google thậm chí là qua username-password trong database. Bạn cũng có thể tùy biến chính xác các route nào cần phải authentication .
    Trong bài viết chúng ta sẽ đi tìm hiểu sâu về cơ chế hoạt động của thằng Passport nhé, bài này giúp bạn tự tin trong việc custom lại nó :D

    Thiết lập Passport cho ứng dụng

    Trước khi đâu sâu vào bên trong, ta hãy ngõ qua cách đề cài đặt passport cho ứng dụng đã. Để sử dụng được passport bạn có tối thiểu 3 bước :

    Require thằng passport và chèn 2 thằng middile của nó vào express là passport.initialize() và passport.session(). Chú ý là ứng dụng express của bạn cần sử dụng đến express-session.

    var app = require('express')();
    var session = require('express-session');
    var bodyParser = require('body-parser');
    var passport = require('passport');
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    
    app.use(session({
      secret : "secret",
      saveUninitialized: true,
      resave: true
    }))
    
    app.use(passport.initialize());
    app.use(passport.session());
    
    app.listen(3333);
    

    Cấu hình 1 kịch bản cho thằng Passport và thiết lập 2 hàm serializeUser , deserializeUser

    var LocalStrategy = require('passport-local').Strategy;
    var passport = require('passport');
    var bcrypt = require('bcrypt');
    
    passport.serializeUser(function(user, done) {
        done(null, user.id);
    });
    
    passport.deserializeUser(function(id, done) {
        db.user.findById(id).then(function (user) {
            done(null, user);
        }).catch(function (err) {
            console.log(err);
        })
    });
    
    passport.use(new LocalStrategy(
        function (username,password,done) {
            db.user.find({where : {
                username : username
            }}).then(function (user) {
                bcrypt.compare(password, user.password, function (err,result) {
                    if (err) { return done(err); }
                    if(!result) {
                        return done(null, false, { message: 'Incorrect username and password' });
                    }
                    return done(null, user);
                })
            }).catch(function (err) {
                return done(err);
            })
        }
    ))
    

    Thiết lập 1 route có dùng middleware passport.authenticate.

    router.route('/login')
        .get(function (req, res) {
            res.render('login', {
                'title': 'Log in'
            })
        })
        post('/login', passport.authenticate('local', { successRedirect: '/',
                                                                                      failureRedirect: '/login' }));
    

    Các bước xác thực tài khoản qua request của người dùng.

    Ví dụ bạn đặt route xác thực tài khoản là /login. Khi người dùng đăng nhập vào vệ thống passport sẽ xử lý như sau:

    1. Khi người dùng submit form đăng nhập, một request POST được tạo ra tới route /login , nó sẽ chạy cái middleware passport.authenticate cho bạn.Như trên ta thiết lập kịch bản Local cho thằng passport.authenticate nên nó sẽ gọi đến cái kịch bản ta đã cài đặt.

    2. Nó lấy dữ liệu req.body.username và req.body.passport rồi gán cho hàm verify local.
      Ở đây như cấu hình ở trên ta thấy chúng ta sẽ query database rồi kiểm tra xem passport của người dùng đưa lên có đúng không.Trong trường hợp Error từ db ta gọi đến callback là done với param là err( done(err)) Khi mà nó không tìm thấy được người dùng hợp lý ta gọi đến thằng done(null,false). Còn nếu thông tin đăng nhập đúng ta gọi done(null,user).Khi callback done được gọi, nó sẽ lấy dữ liệu err, user và dữ liệu bạn custom thêm nếu có trả lại cho thằng passport.authenticate.

    3. Nếu dữ liệu trả về của callback là null, true, xác thực thành công passport tiếp tục gọi hàm req.login( cái này tự gắn vào từng request khi bạn cài đặt passport ở bước 1)

    4. Hàm req.login gọi đến thằng passport.serializeUser mà ta đã định nghĩa trước đó. Hàm này truy cập vào đối tượng user mà ta trả về cho middleware passport.authenticate và xác định xem thành phần nào của đối tượng sẽ lưu vào trong session. Kết quả của hàm này là ta sẽ có đối tượng req.session.passport.user = các thông tin ta truyền vào trong serializeUser.Trong ví dụ bên trên thì nó là user.id. Đồng thời với trên passport cũng có gắn thông tin user vào req.user.

    5. Việc xác thực kết thục, hàm requestHandler sẽ được gọi đưa chúng tra đến trang đã thiết lập
      Xác thực các request sau khi đăng nhập.

    Tất nhiên chúng ta chỉ đăng nhập 1 lần vào hệ thống, không phải cứ làm bất cữ việc gì ta cũng phải điền username - password. Trong các request tiếp theo đến hệ thống passport hoạt động như sau:

    1. Với mỗi request , express sẽ load các sữ liệu trong session ra và gắn nó và đối tượng request (req.session). Ở trên ta đã sử dụng hàm serializeUser để đưa dữ liệu vào session nên ta có thể tìm thấy dữ liệu đó tạo req.session.passport.user.
    2. Middleware khởi động passport (passport.initialize) sẽ check xem trong request session có passport.user không. Nếu chưa có là chưa xác thực thì thằng req.session.passport.user = {}
    3. Tiếp đó passport.session được gọi. Nếu thấy passport.user trong sesion request đó được tính là đã xác thực.
    4. Khi request được tính là đã xác thực nó sẽ gọi hàm passport.deserializeUser. Hàm này sử dụng thông tin trong session để lấy dữ liệu đầy đủ về thằng user rồi gắn nó vào req.user.
      Tổng kết

    Ở đây mình tóm tắt lại các hàm các middleware của passport :

    • passport.initialize : middleware được gọi ở từng request, kiểm tra session lấy ra passport.user nếu chưa có thì tạo rỗng.
    • passport.session: middleware sử dụng kịch bản Passport , sử dụng session lấy thông tin user rồi gắn vào req.user.
    • passport.deserializeUser : hàm được gọi bởi passport.session .Giúp ta lấy dữ liệu user dựa vào thông tin lưu trên session và gắn vào req.user
    • passport.authenticate: middleware giúp ta gắn kịch bản local vào route.
    • passport.serializeUser: hàm được gọi khi xác thực thành công để lưu thông tin user vào session
      Các hàm hỗ trợ thêm cho từng request

    Với từng request passport gắn thêm cho bạn 4 hàm :

    req.login()
    req.logout()
    req.isAuthenticated()
    req.isUnauthenticated()
    

    Tên chúng nói lên tất cả rồi phải không :D
    bài viết lược dịch từ : http://toon.io/understanding-passportjs-authentication-flow/
    nguồn bài viết : http://techmaster.vn/posts/33637/tim-hieu-ve-passportjs-cac-buoc-de-xac-thuc-tai-khoan



  • A cho e hỏi là với một express app route cùng một url , sử dụng cùng rest api thì có cách nào để xác thực được route sẽ sử dụng thông qua role của người dùng không ???

    Giả sử trong đoạn code sau , e muốn user sau khi được authen qua passport . Nếu user có role là "user" thì sẽ vào route1 còn user có role là "admin" thì sẽ vào route2.

    var express = require('express');
    var app = express();
    // route for user
    app.route('/users').get(function(req , res , next) {
    res.send('route1');
    });
    // route for admin
    app.route('/users).get(function(req , res , next) {
    res.send('route2');
    });
    app.listen(8080);



  • @kxd993 ở đây thằng passport có hỗ trợ em 1 thằng req.user > em có thể lấy req.user.roles. Ta tạo 1 cái middleware
    function(req,res,next){
    next(req.user.roles)
    }

    ở hàm xử lý viết dạng function (role, req, res){ với từng role thì để logic riêng, tách các logic ra thành 2 hàm cũng được
    }
    Có rất nhiều cách để thực hiện đây là 1 cách theo anh nghĩ là khá dễ làm



  • @Quốc-Cường Thanks a, do e xem nhầm code một chút vì với điều kiện là cùng route với cùng restAPI thì đúng là k thể chia role được như kiểu của e .



  • Anh cho em hỏi là em có login(vs angular ) và set session cho nó là 6 phút. Làm sao sau 6 phút bên client em chưa logout ra mà session này vẫn còn để em có thể lấy được req.user để gửi 1 post hay get khác.
    Thanks a. em viết hơi khó hiểu và dài



  • @nvs2394 đã nói trong Tìm hiểu về Passport.js: Các bước để xác thực tài khoản:

    Anh cho em hỏi là em có login(vs angular ) và set session cho nó là 6 phút. Làm sao sau 6 phút bên client em chưa logout ra mà session này vẫn còn để em có thể lấy được req.user để gửi 1 post hay get khác.
    Thanks a. em viết hơi khó hiểu và dài

    ực khó hiểu thật . Khi em thiết lập thời hạn cho cookie hoặc session thì tuỳ vào cơ chế của trình duyệt hoặc nó sẽ tự động xoá nó đi không thì nó sẽ không đính kèm khi gửi request lên trên server nữa và em không thể lấy được thông tin này.



  • Hiện giờ em làm như thế này :
    Em dùng passport login cho web. xong nó lưu lại biến req.user để mỗi lần post get get em có thể lấy req.user._id . em có xét trong session là 6 phút. Bên client em GET hàm loggedin để lấy thông tin của user đã login và show ra. Giả sử khi em đang upload 1 tấm hình mà nó hết mất 6 phút thì session đó đã bị hết hạn nhưng người dùng vẫn chưa upload xong



  • @nvs2394 Sao em phải xét set session ngắn như vậy ?



  • Theo ý kiến cá nhân của mình thì bạn có thể format lại bài viết này cho dễ xem hơn, nhìn vào thấy luôn một "wall-of-text", không hấp dẫn lắm cho một bài viết "có thể đã hay".



  • @CodeConCat đã nói trong Tìm hiểu về Passport.js: Các bước để xác thực tài khoản:

    Theo ý kiến cá nhân của mình thì bạn có thể format lại bài viết này cho dễ xem hơn, nhìn vào thấy luôn một "wall-of-text", không hấp dẫn lắm cho một bài viết "có thể đã hay".

    đã sửa lại bài này cũ lắm rồi



  • Cho e hỏi passport khi em dùng với nhiều fields thì nó ko làm việc vậy anh, a có thể giúp e ko ? ví dụ e có bảng account(id, fullname, email, password, phone...)



  • @Tuấn-Phạm-Hoàng phải nhập cả đống field đấy mới cho đăng nhập à

    tobi

    0


  • This post is deleted!

Log in to reply