Heoroku Laravel SSO(LINE)

Giới thiệu

Heroku là một platform build on linux (VPS) rất mạnh mẽ hiện nay và đặc biệt là có thể sử dụng Free. Chúng ta có thể tận dụng đặc điểm này để Build những ứng dụng Demo hay đơn giản chỉ là để Test những chức năng Online.

Hôm nay mình sẽ Build một ứng dụng Web SSO (Single sign on) với Social LINE sử dụng Laravel và deploy lên Heroku.

Chuẩn bị

  • Tạo một tài khoản trên Heroku
  • Repository project Laravel trên Git

Lưu ý source trên Git chúng ta phải chuẩn bị một File để tương tác với Heroku Procfile với nội dung như sau

web: vendor/bin/heroku-php-apache2 public/

và config chạy migrate mối khi deploy trong file composer.json

"scripts": {
        //
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover",
            "@php artisan migrate --force"
        ]
    }
}

Thực hiện

  1. Tạo ứng dụng trên Heroku https://dashboard.heroku.com/apps

Nhập tên App

Deployment method chọn phương thức deployment là Github và bấm nút Connect to Github

Ở mục Connect to Github ta nhập tên Repository muốn deploy lên Heroku vào input và bấm nút Search

Sẽ xuất hiện mục Manual deploy ở đây chúng ta sẽ chọn branch nào sẽ deploy lên Heroku khi chúng ta bấm nút Deploy Branch. Ở đây mình đang chọn ở Branch Master và bấm nút Deploy Branch

OK! Ngay lúc này chúng ta sẽ thấy ngay bên dưới xuất hiện khung Activity đang trong quá trình Deploy source từ Git lên Heroku đợi cho khi process finish là chúng ta đã hoàn tất việc deploy source từ Git lên Heroku rồi.

Việc tiếp theo là chúng ta cần cấu hình biến môi trường sử dụng trên Heroku bởi vì Heroku không đọc file .env

Ở phần Config Vars bấm vào nút Reveal Config Vars

Ở đây chúng ta nhập theo cấu trúc KEY => VALUE như trong file .env

Đên đây là xong rồi chúng ta bấm nút Open app và tận hưởng thành quả nào

  1. Addon service Database postgre

Ở bài này mình sẽ hướng dẫn sử dụng Database Postgres trên Heorku bới vì Addon này free và không cần đăng ký Credit card.

Search tên app và bấm nút Provision add-on

Như vậy chúng ta đã tích hợp Database Postgres vào app Heroku tiếp theo chúng ta lấy thông tin connection của database và thêm vào file .env

  1. Tạo App trên LINE https://developers.line.biz/console/register/provider/

Sau khi tạo xong Auth provider chúng ta quan tâm 3 thông tin sau:

  1. Channel ID
  2. Channel secret
  3. Callback URL

3 thông tin này sẽ cần thiết ở những bước tiếp theo để connect tới LINE

  1. Add chức năng login default của Laravel
php artisan make:auth
  1. Implement Code SSO

Install Laravel Socialite

composerrequirelaravel/socialite

Bổ sung đoạn code bên dưới vào file config/app.php

'providers' => [
    //
    Laravel\Socialite\SocialiteServiceProvider::class,
    App\Providers\Socialite\Line\LineServiceProvider::class
    //
]
//
'aliases' => [
    //
    'Socialite' => Laravel\Socialite\Facades\Socialite::class
    //
]

Tạo table dùng để liên kê ́t LINE Login. Ta sẽ dùng chức năng của Laravel rô`i tạo table.

php artisan make:model LinkedSocialAccount--migration

Edit database/migrations/yyyy_mm_dd_hhiiss_create_linked_social_accounts_table.php như sau

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateLinkedSocialAccountsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('linked_social_accounts', function (Blueprint $table) {
            $table->increments('id');
            $table->bigInteger('user_id');
            $table->string('provider_name');
            $table->string('provider_id');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('linked_social_accounts');
    }
}

Sau khi Edit xong chạy Migration tạo table

php artisan migrate

Tiếp theo edit model User để liên kết với model app/LinkedSocialAccount

public function accounts(){
    return $this->hasMany('App\LinkedSocialAccount');
}

Sau đó từ model LinkedSocialAccount relation với model User

protected $table = 'linked_social_accounts';
protected $fillable = ['provider_name', 'provider_id', 'user_id'];

public function user() {
    return $this->belongsTo('App\User');
}

Tạo Services cho SSO app/Services/SocialAccountsService.php

<?php

namespace App\Services;

use App\User;
use App\LinkedSocialAccount;
use Laravel\Socialite\Contracts\User as ProviderUser;

class SocialAccountsService
{
    public function find(ProviderUser $providerUser, $provider) {
        $account = LinkedSocialAccount::where('provider_name', $provider)
            ->where('provider_id', $providerUser->getId())
            ->first();
        return $account ? $account->user : null;
    }

    public function link(User $user, ProviderUser $providerUser, $provider) {
        if (LinkedSocialAccount::where('provider_name', $provider)->where('user_id', $user->id)->exists()) {
            return null;
        } else {
            $user->accounts()->create([
                'provider_id' => $providerUser->getId(),
                'provider_name' => $provider,
            ]);
            return $user;
        }
    }
}

Tiếp đó, ta sẽ tạo app/Http/Controllers/Auth/SocialAccountController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Services\SocialAccountsService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Log;

class SocialAccountController extends Controller
{

    public function redirectToProvider($provider) {
        return \Socialite::driver($provider)->redirect();
    }

    public function handleProviderCallback(Request $request, SocialAccountsService $accountService, $provider)
    {
        try {
            $user = \Socialite::with($provider)->user();
        } catch (\Exception $e) {
            Log::error($e);
            return redirect()->route('login')->with('status', ['type' => 'error', 'message' => __('customLabel.text_error_500')]);
        }

        $authUser = $accountService->find($user, $provider);

        if ($authUser) {
            auth()->login($authUser, true);
            return redirect()->route('home');
        } else {
            \Session::put("line_user_id", $user->id);
            return redirect()->route('register');
        }
    }
}

Create Provider - Service Provider for LINE

Giờ ta sẽ tổng hợp lại file dùng cho LINE Login, và đặt app/Providers/Socialite/src/ như dưới đây. 。Câ ́u trúc Directory và file tạo sẽ như sau.

app/
    └Socialite/
        └Line/
            ├LineProvider.php
            └LineServiceProvider.php

Vậy trước khi implement, ta sẽ bổ sung phân setting dùng cho LINE.Trong file .env, thêm parameter dùng cho LINE Login. Hãy điêu chỉnh Call-back URL cho phù hợp đểứng với môi trường.Ngoài ra thì bạn cũng đừng quên việc đăng ký vào column Callback URL từ Application Setting Chanel dùng cho LINE Login.Nê ́u không đăng ký là sẽ login thâ ́t bại.

LINE_CLIENT_ID=<Channel ID>
LINE_CLIENT_SECRET=<Channel secret>
LINE_REDIRECT=<Callback URL>

Tạo file app/Providers/Socialite/Line/LineProvider.php

<?php
namespace App\Providers\Socialite\Line;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
class LineProvider extends AbstractProvider implements ProviderInterface
{
    protected $scopes = ['openid', 'profile', 'email'];
    protected $scopeSeparator = ' ';
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://access.line.me/oauth2/v2.1/authorize', $state);
    }
    protected function getTokenUrl()
    {
        return 'https://api.line.me/oauth2/v2.1/token';
    }
    public function getAccessTokenResponse($code)
    {
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
            'form_params' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'redirect_uri' => $this->redirectUrl,
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret
            ],
        ]);
        return json_decode($response->getBody(),true);
    }
    protected function getUserByToken($token)
    {
        $response = $this->getHttpClient()->get('https://api.line.me/v2/profile', [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
            ],
        ]);
        return json_decode($response->getBody(), true);
    }
    protected function mapUserToObject(array $user)
    {
        return (new User())->setRaw($user)->map([
            'id' => $user['userId'],
            'name' => $user['displayName']
        ]);
    }
} 

Tạo file app/Providers/Socialite/Line/LineServiceProvider.php

<?php
namespace App\Providers\Socialite\Line;
use Laravel\Socialite\SocialiteServiceProvider;
class LineServiceProvider extends SocialiteServiceProvider
{
    /**
     * Bootstrap the service provider.
     *
     * @return void
     */
    public function boot() {
        \Socialite::extend('line', function ($app) {
            $config = $this->app['config']['services.line'];
            return \Socialite::buildProvider(LineProvider::class, $config);
        });
    }
} 

Routing Setup & View Preparation

Route::get('/login/{provider}', 'Auth\SocialAccountController@redirectToProvider');
Route::get('/login/{provider}/callback', 'Auth\SocialAccountController@handleProviderCallback');

Tiếp theo chúng ta sẽ thêm link Login LINE vào page login

<a class="btn btn-default" href="/login/line">
    Line Login
</a>

Tiếp theo chung ta sẽ thêm đoạn code Alert Error khi xảy ra lỗi

@if($errors->has('social'))
    <divclass="alert alert-warning">{{$errors->first('social') }}</div>
@endif

đên đây là xong rồi chung ta tận hưởng thành quả thôi

Bấm nút Line Login

Login với tài khoản Line

Và login Success!

Kết luận

Tới đây là chúng ta đã hoàn thành chức năng Login SSO LINE với Laravel trên Heroku . Nếu trong quá trình thực hiện có gì không hiểu hoặc lỗi bạn có thể để lại Comment bên dưới mình sẽ support nhé!

Happy coding!

Posted in Heroku, Line Integration, PHP on Feb 03, 2019