[Laravel] Tích hợp Line login vào project Laravel

Line là một ứng dụng rất phổ biến ở Nhật Bản, số lượng người dùng Line hơn 50 triệu người và chiếm tầm 40% dân số.

Là một ứng dụng sns phổ biến nên việc tích hợp Line vào các dự án (users: Japanese) sẽ giúp web, app có trải nghiệm tốt hơn, tận dụng được các thông tin người dùng được cung cấp từ Line.  Trong bài viết này mình sẽ hướng dẫn tích hợp chức năng login dựa theo document của Line.

Lưu ý: Line hiện tại hỗ trợ 2 phiên bản API là v2.0 và v2.1. Trong bài viết này mình sẽ sử dụng phiên bản v2.1

Giải thích về luồng xử lý Line Login

Luồng xử lý login của Line được dựa trên luồng xử lý của OAuth 2.0 authorization code flow

Để tích hợp được chức năng login chúng ta phải thực hiện đẩy đủ các bước trong flow phía trên. Mình sẽ giải thích sơ qua về flow trên để các bạn hình dung được logic xử lý.

  1. Người dùng lựa chọn chức năng Login with Line ở phía Webapp
  2. Webapp chuyển hướng sang trang đăng nhập của phía Line thông qua 1 authorization url
  3. Phía Line hiển thị màn hình Login
  4. Người dùng nhập các thông tin email và password
  5. Phía Line hiển thị màn hình lựa chọn cung cấp quyền cho phép truy cập thông tin
  6. Người dùng lựa chọn các quyền và accept
  7. Phía Line trả về codestate cho phía Webapp. Ở bước này có thể kiểm tra state gửi đi và state trả về có khớp nhau không để đảm bảo tính bảo mật
  8. Phía Webapp dùng code vừa nhận được để lấy access_token
  9. Phía Line trả về  access_token
  10. Sử dụng access_token để lấy thông tin profile của người dùng từ phía Line

Tích hợp vào project laravel

1. View

Tạo file view cho chức năng login và thêm button Login with Line. Khi click vào button/thẻ a thì sẽ chuyển hướng đến Line Plaform thông qua authorization url ở bước 2 (Link này mình sẽ sinh ra ở controller và đưa nó vào href ở thẻ a)

<div class="col-sm-12">
    <a href="{{$authUrl}}" class="social-button" id="line">
         <span class="badge">
             <img src="{{asset('/images/social-network/line-logo.png')}}" alt="">
         </span>
         <span> LINEでログイン</span>
    </a>
</div>                                          
.social-button {                                                                        
    background-position: 25px 0;
    box-sizing: border-box;
    color: rgb(255, 255, 255);
    cursor: pointer;
    display: inline-block;
    height: 50px;
    line-height: 50px;
    text-align: center;
    text-decoration: none;
    text-transform: uppercase;
    vertical-align: middle;
    width: 100%;
    border-radius: 5px;
    margin: 10px auto;
}

#line {
    background: #00C300;
}

#line span {
    box-sizing: border-box;
    color: #ffffff;
    cursor: pointer;
    text-align: center;
}

.badge {
    display: inline-block;
    float: left;
    width: 60px;
    height: 60px;
    line-height: 60px;
    overflow: hidden;
    text-align: center;
    font-size: 2.6rem;
    border-right: #fff 1px solid;
    margin-right: 0.7em;
    font-weight: normal;
}
                                             

Download logo của LINE tại đây

Mở màn hình trong login và chúng ta sẽ có kết quả như sau:

2. Route

Tạo 2 route cho phần login này. Một route trả về view và một route xử lý dữ liệu do Line trả về (Callback Url).

// Login Line
Route::get('line/login', 'LoginController@redirectToLine')->name('login.line');
// Callback url
Route::get('line/login/callback', 'LoginController@handleLineCallback')->name('login.line.callback');

3. Config

Trong thư mục /config tạo file line.php để định nghĩa các api của Line mà mình sử dụng.

<?php

return [

    'line_profile_uri' => 'https://api.line.me/v2/profile',

    'line_token_uri' => 'https://api.line.me/oauth2/v2.1/token',

    'line_authorize_uri' => 'https://access.line.me/oauth2/v2.1/authorize',

    'line_verify_uri' => 'https://api.line.me/oauth2/v2.1/verify'
];

Trong đó:

  • line_profile_uri        API để lấy thông tin Profile người dùng
  • line_token_uri            API để lấy access_token từ like sau khi đăng nhập
  • line_authorize_uri    API chuyển hướng đăng nhập Line
  • line_verify_uri          API để xác minh ID_token trả về từ Line và lấy thông tin Profile (Chỉ sử dụng cho v2.1)

Đăng nhập tài khoản Line Developer và cài đặt Line Login

Sau khi tạo xong Provider và thêm Line Login trên tài khoản developer, lấy các thông tin (Channel ID và Channel Secret) thêm vào .env của Laravel

LINE_LOGIN_CHANNEL_ID = '****'
LINE_LOGIN_CHANNEL_SECRET = '*****'

Ngoài ra, bạn cần thêm callback URL vào Line Login. Phía line sẽ kiểm tra redirect_url trong authorization_url với callback URL đã thêm ở phía Line có khớp với nhau không.

Callback URL trong trường hợp này (là route thứ 2 phía trên) có dạng như:

  • Local: http://127.0.0.1:8000/line/login/callback
  • Serve: https://domain.com/line/login/callback

4. Service

Để tách xử lý logic ra khỏi controller, mình sẽ tạo 1 service chỉ xử lý các logic liên quan đến Line.

<?php

namespace App\Services;


use GuzzleHttp\Client;
use Illuminate\Support\Carbon;

class LineService
{

    public function getLoginBaseUrl()
    {
        $currentTime = Carbon::now()->getTimestamp();
        $url = config('line.line_authorize_uri') . '?';
        $url .= 'response_type=code';
        $url .= '&client_id=' . env('LINE_LOGIN_CHANNEL_ID');
	      $url .= '&redirect_uri=' . route('login.line.callback');
        $url .= '&state=' . $currentTime;
        $url .= '&scope=openid%20profile%20real_name%20gender%20birthdate%20phone%20address%20email';

        return $url;
    }

    public function getLineToken($code)
    {
        $client = new Client();
        $response = $client->post(config("line.line_token_uri"), [
            'form_params' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'redirect_uri' => route('login.line.callback'),
                'client_id' => env('LINE_LOGIN_CHANNEL_ID'),
                'client_secret' => env('LINE_LOGIN_CHANNEL_SECRET')
            ]
        ]);
        return json_decode($response->getBody()->getContents(), true);
    }

    public function getUserProfile($token)
    {
        $client = new Client();
        $headers = [
            'Authorization' => 'Bearer ' . $token,
            'Accept'        => 'application/json',
        ];
        $response = $client->get( config('line.line_profile_uri'), [
            'headers' => $headers
        ]);
        return json_decode($response->getBody()->getContents(), true);
    }

    public function verifyIDToken($idToken) {
        $client = new Client();
        $response = $client->post(config("line.line_verify_uri"), [
            'form_params' => [
                'id_token' => $idToken,
                'client_id' => env('LINE_LOGIN_CHANNEL_ID'),
            ]
        ]);
        return json_decode($response->getBody()->getContents(), true);
    }
}

Trong đó:

getLoginBaseUrl()

  • Tạo authorization requests chuyển sang trang đăng nhập của LINE để đăng nhập tài khoản.
  • API tham khảo: Make authorization request

getLineToken($code)

  • Sau khi login với LINE thì callback trả về sẽ chứa code. Từ code sẽ gọi đến api get access token để lấy access_token
  • API tham khảo: Get access token

getUserProfile($token)

  • Lấy thông tin profile cơ bản của người dùng
  • API tham khảo: Get user profile

verifyIDToken($idToken)

  • Lấy thông tin profile từ ID Token (Có chứa thông tin line profile+)
  • API tham khảo: Verify ID token

5. Controller

Tạo một controller cho phần Line Login.

Do các logic xử lý đã được đưa vào service nên file controller sẽ rất gọn.

<?php

namespace App\Http\Controllers;

use App\Services\LineService;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\RedirectResponse;

class LoginController extends Controller
{

    /** @var  LineService */
    protected $lineService;

    /**
     * LoginController constructor.
     * @param LineService $lineService
     */
    public function __construct(LineService $lineService)
    {
        $this->lineService = $lineService;
    }

    /**
     * Login Blade
     * @param Request $request;
     * @return View
     */
    public function index(Request $request) {
        $authUrl = $this->lineService->getLoginBaseUrl();

        return view('login.line', compact('authUrl'));
    }

    /**
     * Handle result which Line API returned.
     * @param Request $request
     * @return void
     */
    public function handleLineCallback(Request $request) {
        $code = $request->input('code', '');
        $response = $this->lineService->getLineToken($code);
        // Get profile from access token.
        $profile = $this->lineService->getLineToken($response['access_token']);
        // Get profile from ID token
        $profile = $this->lineService->verifyIDToken($response['id_token']);
  }
}

👏👏👏Thanks for taking the time to read my article !!