Xác thực đăng nhập bằng Passport – Facebook

Bài viết này là một phần của series bài viết Xác thực đăng nhập bằng Passport:

  1. Bắt đầu với Local Authentication
  2. Facebook (Bạn đang ở đây)
  3. Twitter
  4. Google
  5. Liên kết các tài khoản

Chào mừng tới phần 2 của series Xác thực đăng nhập bằng Passport. Ở bài này chúng ta sẽ sử dụng xác thực với facebook cho ứng dụng. Sau khi hoàn thành phần 1, chúng ta đã một cấu trúc chương trình đầy đủ với các gói thư viện cần dùng, user model, cấu hình ứng dụng và các views.

Chúng ta đã tạo ra một chương trình với cấu trúc khá rõ ràng và ngắn gọn, ở bài này những gì chúng ta phải làm sẽ là tạo một Facebook app, sửa các file sau:

Lưa thông tin cấu hình Facebook app: ClientId, Secret config/auth.js
Cấu hình Facebook Strategy cho Passport config/passport.js
Khởi tạo route app/routes.js
Cập nhật view views/

Trong bài không nó rõ các bước tạo một App Facebook, tôi mặc định các bạn đã làm được bước này.

Xác thực với Facebook sử dụng Passport

Tạo ứng dụng Facebook

Đầu tiên, chúng ta vào trang Facebook Developers Portal và tạo một ứng dụng. Các thông tin chúng ta cần lấy là AppId và AppSecret để thực hiện việc xác thực với Facebook.

Cài đặt Callback URL là http://localhost:8080/auth/facebook/callback . Đây sẽ là url Facebook sẽ gọi lại khi người dùng đã xác thực thành công ứng dụng.

Ứng dụng thật(Public) với Sanbox(bản thử nghiệm): Nếu bạn để ứng dụng Facebook của bạn ở chế độ Sanbox thì chỉ bạn mới có thể xác thực được. Public thì mọi người đều có thể xác thực.

Giờ bạn đã có thông tin về Facebook app: AppId và Secret, hãy điền nó vào file config. Chúng ta cũng sẽ để những trường tương tự cho Twitter và Google.

// config/auth.js

module.exports = {

    'facebookAuth': {
        'clientID': '182734854xxxxxxx', // App ID của bản
        'clientSecret': '7e7b945a5df7c7da047ee5577xxxxxxx', // App Secret của bạn
        'callbackURL': 'http://localhost:8080/auth/facebook/callback'
    },

    'twitterAuth': {
        'consumerKey': 'your-consumer-key-here',
        'consumerSecret': 'your-client-secret-here',
        'callbackURL': 'http://localhost:8080/auth/twitter/callback'
    },

    'googleAuth': {
        'clientID': 'your-secret-clientID-here',
        'clientSecret': 'your-client-secret-here',
        'callbackURL': 'http://localhost:8080/auth/google/callback'
    }

};

Giờ chúng ta sẽ thiết lập Facebook Strategy.

Cấu hình Facebook Strategy cho Passport config/passport.js

Giờ để ứng dụng của chúng ta có thể sử dụng xác thực của Facebook, chúng ta sẽ phải cấu hình Facebook Strategy. Strategy này sẽ sử dụng xác thực của Facebook và xử lý dữ liệu khi xác thực Facebook thành công.

Chúng ta đã có code cho Local Strategy ở file này, chúng ta sẽ thêm phần Facebook Strategy ở ngay bên dưới.

// config/passport.js

// load những thứ chúng ta cần
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;

// Lấy thông tin những giá trị auth
var configAuth = require('./auth');

// load  user model
var User = require('../app/models/user');

module.exports = function (passport) {

    // =========================================================================
    // passport session setup ==================================================
    // =========================================================================
    // required for persistent login sessions
    // passport needs ability to serialize and unserialize users out of session

    // used to serialize the user for the session
    passport.serializeUser(function (user, done) {
        done(null, user.id);
    });

    // used to deserialize the user
    passport.deserializeUser(function (id, done) {
        User.findById(id, function (err, user) {
            done(err, user);
        });
    });

    // code for login (use('local-login', new LocalStategy))
    // code for signup (use('local-signup', new LocalStategy))

    // =========================================================================
    // FACEBOOK ================================================================
    // =========================================================================
    passport.use(new FacebookStrategy({
            // điền thông tin để xác thực với Facebook.
            // những thông tin này đã được điền ở file auth.js
            clientID: configAuth.facebookAuth.clientID,
            clientSecret: configAuth.facebookAuth.clientSecret,
            callbackURL: configAuth.facebookAuth.callbackURL,
            profileFields: ['id','displayName','email','first_name','last_name','middle_name']
        },

        // Facebook sẽ gửi lại chuối token và thông tin profile của user
        function (token, refreshToken, profile, done) {
            // asynchronous
            process.nextTick(function () {
                // tìm trong db xem có user nào đã sử dụng facebook id này chưa
                User.findOne({'facebook.id': profile.id}, function (err, user) {
                    if (err)
                        return done(err);

                    // Nếu tìm thấy user, cho họ đăng nhập
                    if (user) {
                        return done(null, user); // user found, return that user
                    } else {
                        // nếu chưa có, tạo mới user
                        var newUser = new User();

                        // lưu các thông tin cho user
                        newUser.facebook.id = profile.id;
                        newUser.facebook.token = token;
                        newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName; // bạn có thể log đối tượng profile để xem cấu trúc
                        newUser.facebook.email = profile.emails[0].value; // fb có thể trả lại nhiều email, chúng ta lấy cái đầu tiền

                        // lưu vào db
                        newUser.save(function (err) {
                            if (err)
                                throw err;
                            // nếu thành công, trả lại user
                            return done(null, newUser);
                        });
                    }

                });
            });

        }));

};

Profile: Object chứ thông tin user của dịch vụ(Facebook, Twitter, Google) sẽ có cấu trúc thông tin khác nhau, nhưng Passport đã xử lý về một đối tượng để dễ xử lý user profile doc.

Chúng ta sẽ sử dụng Strategy cho việc xác thực với Facebook và xử lý khi Facebook trả lại thông tin. Chúng ta sẽ lưu thông tin user và chuỗi token để truy cập các dịch vụ của Facebook.

Giờ chúng ta sẽ thiết lập các routes.

Routes app/routes.js

Hãy nhớ khi chúng ta thiết lập ứng dụng, đối tượng passport đã được chuyển từ server.js tới file config/passport.js. Cuối cùng nó được chuyển qua các file routes. Đó là lý do tại sao chúng ta chỉ thêm Facebook Stategy ở file config mà nó lại hoạt động được ở file route.

Ở phần này chúng ta cần 2 routes: Một cho việc yêu cầu xác thực với Facebook, một cho việc xử lý callback.

  • /auth/facebook: Gửi yêu cầu của chúng ta tời Facebook để yêu cầu xác thực.
  • /auth/facebook/callback: Facebook gửi lại ứng dụng của chúng ta chuỗi token và thông tin user.
// app/routes.js
...
    // =====================================
    // FACEBOOK ROUTES =====================
    // =====================================
    // yêu cầu xác thực bằng facebook
    app.get('/auth/facebook', passport.authenticate('facebook', {scope: ['email']}));

    // xử lý sau khi user cho phép xác thực với facebook
    app.get('/auth/facebook/callback',
        passport.authenticate('facebook', {
            successRedirect: '/profile',
            failureRedirect: '/'
        })
    );
...

Mặc định, Facebook sẽ trả lại thông tin user, nhưng không có thông tin email. Chúng ta sẽ thêm một scope là email. Bạn có thể thêm vào những scope khác để lấy thêm thông tin.

Khi xây dựng ứng dụng đăng nhập với Facebook, cố gắng chỉ sử dụng những thông tin mà bạn cần. Như vậy người dùng mới có thể thoải mái với quyền riêng tư khi sử dụng ứng dụng của bạn.

Hiển thị thông tin user index.ejs, profile.ejs

Chúng ta đã có các routes, chúng ta chỉ cần cung cấp cho người dùng một cái nút để đăng nhập với Facebook. Tiếp theo, sau khi họ đã xác thực và được chuyên tới trang riêng tư – profile, chúng ta sẽ hiển thị thông tin của user ở đây.

Nút đăng nhập với Facebook

Chúng ta sẽ thêm nút đăng nhập với facebook vào file index.ejs

<!-- views/index.ejs -->
...
        <a href="/signup" class="btn btn-default"><span class="fa fa-user"></span> Local Signup</a>
        <a href="/auth/facebook" class="btn btn-primary"><span class="fa fa-facebook"></span> Facebook</a>
...

Khi user  click vào nút đó, họ sẽ truy cập tới route /auth/facebook nơi họ sẽ được chuyển tới Facebook Strategy. Họ sẽ gửi tới Facebook một yêu cầu xác thực.

Nếu một user nhấn Cancel, họ sẽ được chuyển về trang chủ. Đó là cài đặt được đặt ở failureRedirect của route /auth/facebook/callback. Nếu việc xác thực thành công, thông tin user sẽ được lưu lại và họ được chuyển tới trang /profile .

Trang Profile views/profile.ejs

Cuối cùng chúng ta sẽ hiển thị thông tin user ở trang profile.

Chúng ta có thể thấy user ở trong db:

Giờ chúng ta hiển thị thông tin ra ngoài trang profile

<!-- views/profile.ejs -->
...
        <!-- FACEBOOK INFORMATION -->
        <div class="col-sm-6">
            <div class="well">
                <h3 class="text-primary"><span class="fa fa-facebook"></span> Facebook</h3>

                    <p>
                        <strong>id</strong>: <%= user.facebook.id %><br>
                        <strong>token</strong>: <%= user.facebook.token %><br>
                        <strong>email</strong>: <%= user.facebook.email %><br>
                        <strong>name</strong>: <%= user.facebook.name %>
                    </p>

            </div>
        </div>
...

Hoàn thành!

Ở bài tiếp chúng ta sẽ cũng xây dựng xác thực với Twitter.