LoginSignup
27
29

More than 1 year has passed since last update.

Laravel API SNSログイン機能を実装する

Last updated at Posted at 2022-05-07

Laravel Socialite

Laravelの公式からOAuthプロバイダで認証するための便利なライブラリが提供されています。

サポートプラットフォーム

Socialiteは現在下記のプロバイダをサポートしています。

  • Facebook
  • Twitter
  • LinkedIn
  • Google
  • GitHub
  • GitLab
  • Bitbucket

この他にもコミュニティで管理されている150以上のプロバイダがあります。

Socialiteのインストール

$ composer require laravel/socialite

環境

実装

GitHubとSNSログインする場合を例に実装します。

.env

.env
APP_URL=http://localhost

SESSION_DRIVER=cookie
SESSION_LIFETIME=120

GITHUB_CLIENT_ID=1234567890abcdefghij
GITHUB_CLIENT_SECRET=1234567890abcdefghijklmnopqrstuvwxyz

GITHUB_CLIENT_IDGITHUB_CLIENT_SECRET の取得手順は後述します。

config/services.php

config/services.php
<?php declare(strict_types=1);

return [
    // ...

    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('APP_URL') . '/oauth/github/callback',
    ],
];

マイグレーション

デフォルトの users テーブルを利用します。

database/migrations/2014_10_12_000000_create_users_table.php
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            // ...
            $table->string('name')->nullable();
            $table->string('email')->unique()->nullable();
            // ...
        });
    }

SNS連携からユーザー登録する場合、名前やメールアドレスを取得できないプロバイダーがある場合があります。
nameemail はNULL許可にしておきます。(ここは要件に応じてケースバイケースで変えてください。)

$ php artisan migrate:fresh

ソーシャルプロバイダーを管理する social_accounts テーブルを定義します。
連携するプロバイダーが一つだけとか決まっている場合は users テーブルにカラムを設けてもいいかもしれません。

$ php artisan make:migration create_social_accounts_table
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('social_accounts', function (Blueprint $table) {
            $table->foreignId('user_id')->constrained();
            $table->string('provider_name');
            $table->string('provider_id');
            $table->string('provider_token');
            $table->string('provider_refresh_token')->nullable();
            $table->timestamps();
            $table->unique(['user_id', 'provider_name']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('social_accounts');
    }
};
$ php artisan migrate

モデル

$ php artisan make:model SocialAccount
app/Models/SocialAccount.php
<?php declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

final class SocialAccount extends Model
{
    use HasFactory;

    public $incrementing = false;

    protected $primaryKey = ['user_id', 'provider_name'];
    protected $guarded = ['created_at', 'updated_at'];

    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

app/Models/User.php に追記します。

app/Models/User.php
use Illuminate\Database\Eloquent\Relations\HasMany;

final class User extends Authenticatable
{
    // ...

    /**
     * @return HasMany
     */
    public function socialAccounts(): HasMany
    {
        return $this->hasMany(SocialAccount::class);
    }
}

対応するプロバイダーのEnumを作る

$ mkdir app/Enums
$ touch app/Enums/SocialProvider.php
app/Enums/SocialProvider.php
<?php declare(strict_types=1);

namespace App\Enums;

enum SocialProvider: string
{
    case GitHub = 'github';
}

対応するプロバイダーを増やしたい時はここのEnumを増やします。
コントローラで暗黙のEnumバインディングとして受け取れます。

存在しないプロバイダーが指定された時は404エラーになります。

コントローラ

$ php artisan make:controller -i OAuth/RedirectToProviderController
$ php artisan make:controller -i OAuth/CallbackFromProviderController
app/Http/Controllers/OAuth/RedirectToProviderController.php
<?php declare(strict_types=1);

namespace App\Http\Controllers\OAuth;

use App\Enums\SocialProvider;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Laravel\Socialite\Facades\Socialite;

final class RedirectToProviderController extends Controller
{
    /**
     * @param SocialProvider $provider
     * @return RedirectResponse
     */
    public function __invoke(SocialProvider $provider): RedirectResponse
    {
        return Socialite::driver($provider->value)->redirect();
    }
}
app/Http/Controllers/OAuth/CallbackFromProviderController.php
<?php declare(strict_types=1);

namespace App\Http\Controllers\OAuth;

use App\Enums\SocialProvider;
use App\Http\Controllers\Controller;
use App\Models\SocialAccount;
use App\Models\User;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as SocialiteTwoUser;

final class CallbackFromProviderController extends Controller
{
    /**
     * @param AuthManager $auth
     */
    public function __construct(
        private AuthManager $auth,
    ) {
    }

    /**
     * @param Request $request
     * @param SocialProvider $provider
     * @return JsonResponse
     */
    public function __invoke(Request $request, SocialProvider $provider): JsonResponse
    {
        /** @var SocialiteTwoUser $socialiteTwoUser */
        $socialiteTwoUser = Socialite::driver($provider->value)->user();

        $socialAccount = SocialAccount::where([
            'provider_name' => $provider->name,
            'provider_id' => $socialiteTwoUser->getId(),
        ])->first();

        if ($socialAccount) {
            $this->auth->guard()->login($socialAccount->user);
            $request->session()->regenerate();

            return new JsonResponse([
                'data' => [
                    'id' => $socialAccount->user->id,
                    'name' => $socialAccount->user->name,
                    'email' => $socialAccount->user->email,
                ],
                'message' => 'Already social linked.',
            ]);
        }

        $user = User::firstOrCreate([
            'email' => $socialiteTwoUser->getEmail(),
        ], [
            'name' => $socialiteTwoUser->getName(),
            'password' => Hash::make(Str::random()),
        ]);

        $user->socialAccounts()->create([
            'provider_name' => $provider->value,
            'provider_id' => $socialiteTwoUser->getId(),
            'provider_token' => $socialiteTwoUser->token,
            'provider_refresh_token' => $socialiteTwoUser->refreshToken,
        ]);

        $this->auth->guard()->login($user);
        $request->session()->regenerate();

        return new JsonResponse([
            'data' => [
                'id' => $user->id,
                'name' => $user->name,
                'email' => $user->email,
            ],
            'message' => 'Successful social link.',
        ]);
    }
}
  • 既にSocialAccount作成済みの場合は認証させて正常レスポンスを返す
  • Userが存在しなければ連携先の情報を元にUserを作成する
  • SocialAccountを作成してUserを認証させて正常レスポンスを返す

この辺りの実装は仕様によると思います。
お好みで書き換えてください。

ルーティング

routes/web.php
<?php declare(strict_types=1);

use App\Http\Controllers\OAuth\CallbackFromProviderController;
use App\Http\Controllers\OAuth\RedirectToProviderController;

Route::get('/oauth/{provider}/redirect', RedirectToProviderController::class)->name('oauth.redirect');
Route::get('/oauth/{provider}/callback', CallbackFromProviderController::class)->name('oauth.callback');

GitHub OAuth 設定手順

Settings > Developer settings > OAuth Apps > Register a new application にアクセスします。

ScreenShot 2022-05-07 0.41.52.png

ScreenShot 2022-05-07 0.48.53.png

ScreenShot 2022-05-07 0.49.08.png

  • Application name
  • Homepage URL
  • Authorization callback URL

を入力します。

ScreenShot 2022-05-08 4.24.36.png

アプリケーションを作成するとClient IDが表示されることを確認します。

ScreenShot 2022-05-07 0.57.25.png

Client secrets の Generate a new client secret をクリックします。

ScreenShot 2022-05-07 1.09.20.png

Client secrets が表示されることを確認します。
(次回以降表示されなくなるのでコピー忘れずに。)

.envのGITHUB_CLIENT_IDとCLIENT_SECRETに追記します。

.env
GITHUB_CLIENT_ID=1234567890abcdefghij
GITHUB_CLIENT_SECRET=1234567890abcdefghijklmnopqrstuvwxyz

詳しくはGitHubの公式ドキュメントを参照してください。

環境変数の設定確認します。

$ php artisan tinker

config('services.github');

動作確認

ScreenShot 2022-05-08 4.35.52.png

ScreenShot 2022-05-08 4.36.19.png

認証するとコールバックのルートへリダイレクトされます。

http://localhost/oauth/github/callback?code=c721fe70faa13d59b942&state=aL0mn7MC49WOEENGFunqupoJ6gKI7iqvFHdJPZLa

成功すると下記のJSONレスポンスが返ります。

{
	"data": {
		"id": 1,
		"name": "your-name",
		"email": "your-name@example.com"
	},
	"message": "Successful social link."
}

SQLを実行してテーブルにデータが登録されていることを確認します。

> select * from users\G;
*************************** 1. row ***************************
               id: 1
             name: ucan-lab
            email: your-name@example.com
email_verified_at: NULL
         password: $2y$10$qIbmZs/4QRNFuM8xSrXe3.T5S0fTjDwQe9qHecR.MFFGdGqvyHDJW
   remember_token: NULL
       created_at: 2022-05-07 19:15:38
       updated_at: 2022-05-07 19:15:38
1 row in set (0.00 sec)

> select * from social_accounts\G;
*************************** 1. row ***************************
               user_id: 1
         provider_name: github
           provider_id: 123456789
        provider_token: 123456789abcdefghijklmnopqrstuvwxyz
provider_refresh_token: NULL
            created_at: 2022-05-07 19:15:38
            updated_at: 2022-05-07 19:15:38
1 row in set (0.00 sec)

他のプロバイダーを追加したい場合

下記の手順で追加できます。

  • 各種プロバイダーでアプリケーションを作成
  • .env に各種クライアントIDとクライアントシークレットを追加
  • config/services.php に各種クライアントの設定を追加
  • app/Enums/Provider.php に各種クライアント名を追加

LINEプロバイダーを追加する手順

LINEは公式ではなくコミュニティで管理されているので、コミュニティのプロバイダーを追加する手順をご紹介します。

追加手順は各種プロバイダーのインストール手順を参考にして進めます。

$ composer require socialiteproviders/line

config/services.php にLINEプロバイダーを追加します。

config/services.php
    'line' => [    
        'client_id' => env('LINE_CLIENT_ID'),  
        'client_secret' => env('LINE_CLIENT_SECRET'),  
        'redirect' => env('APP_URL') . '/oauth/line/callback',
    ],
app/Providers/EventServiceProvider.php
    protected $listen = [
        \SocialiteProviders\Manager\SocialiteWasCalled::class => [
            // ... other providers
            \SocialiteProviders\Line\LineExtendSocialite::class . '@handle',
        ],
    ];
app/Enums/Provider.php
<?php declare(strict_types=1);

namespace App\Enums;

enum Provider: string
{
    case GitHub = 'github';
    case LINE = 'line';
}

環境変数の設定確認します。

$ php artisan tinker

config('services.line');

LINE Developers

ScreenShot 2022-05-09 22.30.15.png

LINEアカウントもしくはビジネスアカウントを作成してログインします。
今回はLINEアカウントを利用します。仕事で使う場合はビジネスアカウントを作成しましょう。

LINEアカウントでログインすると開発用アカウントの登録を求められます。

ScreenShot 2022-05-09 22.32.19.png

開発者登録が完了すると開発コンソール画面が表示されます。

ScreenShot 2022-05-09 22.33.29.png

まずはプロバイダーを作成します。

ScreenShot 2022-05-09 22.34.06.png

適当にアプリケーション名を入れてプロバイダーを作成します。

ScreenShot 2022-05-09 22.34.50.png

次に Create a LINE Login channel からログインチャンネルを作成します。

ScreenShot 2022-05-09 22.34.50.png

Create a new channel

チャンネルの作成では下記の項目の入力を求められます。

  • Channel type: LINE Login
  • Provider: 作成したプロバイダー名を選択
  • Region: Japan
  • Company or owner's country or region: Japan
  • Channel icon: 任意
  • Channel name: 適当
  • Channel description: 適当
  • App types: Web app (今回はMobile appは選択しない)
  • Email address: your-email@example.com
  • Privacy policy URL: 任意
  • Terms of use URL: 任意

ScreenShot 2022-05-09 22.45.14.png

ScreenShot 2022-05-09 22.45.57.png

作成されたチャンネル画面から Channel IDChannel secret の値を .env に設定します。

.env
LINE_CLIENT_ID=1234567890
LINE_CLIENT_SECRET=1234567890abcdefghijklmnopqrstuv

LINE Login タブから Web appCallback URL を設定します。

ScreenShot 2022-05-09 22.48.22.png

コールバックURL: http://localhost/oauth/line/callback

【2022.09.17 追記】

localhost はコールバックURLとして設定できなくなっていました。

ScreenShot 2022-09-17 18.16.14.png

/etc/hosts ファイルを書き換えて

127.0.0.1    example.com

コールバックURL: https://example.com/oauth/line/callback
と指定するとLINEログインできます。(一応ローカルSSLしなくても大丈夫みたいです。)

もしくはngrokを使ってngrokの生成したURLを使用します。
前にngrokの記事を書いているのでよければ参考にしてください。

動作確認

ScreenShot 2022-05-09 23.24.43.png

ScreenShot 2022-05-09 23.25.03.png

正常レスポンスが返ってきたので成功です。

27
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
29