Làm thứ làm cho Laravel Request của bạn thú vị hơn

Laravel Auth::logoutOtherDevices và câu chuyện hài

Hi all,

Đây là câu chuyện của bản thân mình, note lại để nhớ =))

1/ Problems

Mình muốn implement cái kiểu gọi là đăng xuất tất cả các thiết bị related đến users trừ cái session hiện tại. Kiểu như khi login ở browser A, rồi qua browser B login tiếp thì browser A sẽ bị invalidated => fải login lại mới overwrite dc cái browser B.

Mình biết, là từ Laravel 5.6, đã có cái method gọi là logoutOtherDevices thuộc trong Auth facade. (More info: https://laravel.com/docs/8.x/authentication#invalidating-sessions-on-other-devices)

Ok, có rồi xài lun vì nó xịn sẵn rồi. Các bạn nên chú ý, khi xài framework (nhất là mấy cái nổi nổi, tech-trending,…) thì bản thân các features, methods,… của nó đã được test và viết test rất là kỹ càng rồi. Hãy ưu tiên sử dụng built-in của Framework, trừ khi mà nó ko có cái bạn muốn, thì hãy tính đến invent the wheel. Cụ thể những cái Authentication và Authorization.

Problem bắt đầu gặp fải khi mình xài Laravel 8.x. Authentication thì được ship riêng biệt bởi Laravel Jetsteam (mình fải cài thằng này). Và các Auth Controller Classes thì ko nằm trong app/Http/Controllers nữa (nằm trong vendor thuộc Jetsteam). Ok chơi luôn.

Thì hồi đó, để process gì gì đó đó sau khi logged in, trong LoginController, chúng ta sẽ override lại method có tên là authenticate. Giờ thì ko có nữa rồi, nên chúng ta fải solve vụ đó qua Event Handling của Laravel. Bản thân thằng Auth cũng có 1 mớ events đi kèm và sẽ được invoke.

Và mình listen đến cái event này: Illuminate\Auth\Events\Authenticated. Đọc qua tên cảm thấy chắc nó rồi, After Logged In chắc luôn =)). Trong handle method, đơn giản là mình  gọi tới

Auth::logoutOtherDevices($request->post('password'))

Và khi mình test, vâng, toàn bị logout và login ko dc nữa vì sai password =))

Sau một hồi, nhảy vào đọc source luôn, source như sau:

/**
     * Invalidate other sessions for the current user.
     *
     * The application must be using the AuthenticateSession middleware.
     *
     * @param  string  $password
     * @param  string  $attribute
     * @return bool|null
     */
    public function logoutOtherDevices($password, $attribute = 'password')
    {
        if (! $this->user()) {
            return;
        }

        $result = tap($this->user()->forceFill([
            $attribute => Hash::make($password),
        ]))->save();

        if ($this->recaller() ||
            $this->getCookieJar()->hasQueued($this->getRecallerName())) {
            $this->queueRecallerCookie($this->user());
        }

        $this->fireOtherDeviceLogoutEvent($this->user());

        return $result;
    }

Hmm, mình thấy được là cái hash password sẽ thay đổi (hash AES256 ko fixed như md5,sha1,…) và updated liền vào database. Đoạn dưới về solving cookie nên bỏ qua. Từ đó mình thấy cái flow process sẽ là: nếu password hash của session khác mà khác với thực tại (đã updated trong database) => nó đã bị invalidated => logout ngay khi chuyển trang.

Thế thì đúng chứ nhỉ, vì mình cần vậy mà =)). Nên quyết định sử dụng hàm quốc dân dd để xem cái Event Listener nó chạy ra sao.

Và mới phát hiện ra rằng, thằng Authenticated event nó được chạy liên tục (ở mỗi request), để cho ta biết được session hiện tại là logged in. Vậy nên mình dùng $request->post ở trên, nó sẽ ko có gì, trả về null => null được hash lại và save vào database => sai password sau khi bị logged out. Oh my god :))

Cái Event thực sự mình cần, là sau khi logged in, 1 lần duy nhất. Và tiếp tục mò trong vendor, thấy nghi nghi mỗi thằng Illuminate\Auth\Events\Login (vì mấy thằng khác toàn tên dị dị =)) ). Vác ra xài thử, vâng, đúng là nó. Được invoke ngay sau khi logged in successfully.

2/ Solution

Chuyển qua listen tới Event này: Illuminate\Auth\Events\Login

Sau đó mọi thứ chạy theo những gì mình mong muốn. Login A, B out. Login B, A out.

// New Update

Mình gần như nhận ra rằng, Login Event nó sẽ dc fired gần như là thường xuyên, kể cả khi mình ko login. Vì mình gặp fải trường hợp là login ko dc khá là frequently.

Cho đến nay mình đang follow theo JetSteam của Laravel, nên vẫn dò source code thử =)). ĐỢi minnh2 update lại nhé.

3/ Cái kết

Cũng rất là ngọt ngào =)), dẫu sao thì mình cũng đạt những gì mình muốn và điều đó đã dc built-in sẵn trong Laravel hehe.

Cám ơn các bạn đã đọc story này.

facebook
Seth Phát

Seth Phát

Mình là Phát - biệt danh Seth Phát. Hiện đang là một Sr. Full-Stack Engineer. Mình là một người yêu thích và đam mê lập trình và hiện tại đang theo về phần Web là chủ yếu. Mạnh Back-end và khá Front-end, vẫn đang theo đều cả 2 :v. Còn gì bằng khi được làm những thứ mà mình yêu thích, đam mê ;)

Leave a Reply

Your email address will not be published. Required fields are marked *

Bình luận qua Facebook