LoginSignup
8
2

More than 1 year has passed since last update.

この記事はAll About Group(株式会社オールアバウト) Advent Calendar 2022 8日目の記事です。

社内にタスク管理アプリがあり、ログイン機能を追加できたらと考えておりました。

そこで会社のGithubアカウントやGoogleアカウントを紐付けてユーザー管理できないかと考えてOAuth認証ができる方法を調べてみました。

OAuth認証とは

TwitterやFacebook、Googleなどの外部サービスのアカウントをログイン認証に使い、アプリケーション側ではユーザー情報だけを管理する仕組みをOAuth認証と言います。

Socialite

Socialiteとは

LaravelでSocialiteパッケージを使うことでOAuth認証を実装することができます。

Facebook、Twitter、Google、LinkedIn、GitLab、 Bitbucket等に対応しています。

提供していないサービスであっても独自のOAuth認証ドライバを用意することで利用が可能です。

Socialiteのインストール

Socialiteパッケージをインストールするにはアプリケーションのルートディレクトリ(composer.jsonが設置されているディレクトリ)で下記のコマンドを実行します。

composer require laravel/socialite

GitHub OAuth認証をWebアプリで使用するための設定

GitHub上でOAuth認証の準備

GitHub上でOAuthアプリケーションの作成を行います。

GitHubにログインして、[Settings]→[Developer Settings]→[OAuth Apps]→[New OAuth App]と画面遷移します。

以下の3項目を記入していきます。

  1. Application Name(Webアプリケーションの名前)
  2. Homepage URL(ホームページのURL)
  3. Authorization callback URL(GitHubで認証が完了した後の戻り先のURL)

image.png

Webアプリ側でOAuth認証の準備

GitHubへのアプリケーション登録後に作成されるClitent IDとClient Secretの値をconfig/service.phpに記述します。

config/service.php
'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => env('GITHUB_CALLBACK_URL'),
] 

.env関数から参照するようにしています。

こちらにはGitHubで取得した値を記述してください。

.env
GITHUB_CLIENT_ID='Client ID'
GITHUB_CLIENT_SECRET='Client secrets'
GITHUB_CALLBACK_URL="${APP_URL}/social-auth/github/callback"

マイグレーションファイルの作成

user情報を登録するテーブルを作成していきます。

パスワードはnullableにすることで、存在しなくても登録できるようにしています。

userテーブルははじめから存在してるマイグレーションファイルを今回は編集しました。

database/migrations/xxxxxx_create_users_table.php
<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

またどのソーシャルアカウントを使っているかを管理するためのSocialAccountsテーブルを作成します。

php artisan make:migration create_social_accounts_table
/database/migrations/xxxx_xx_xx_xxxxxx_create_social_accounts_table.php
<?php

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

class CreateSocialAccountsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('social_accounts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->string('provider_name')->nullable();
            $table->string('provider_id')->unique()->nullable();
            $table->timestamps();
        });
    }

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

編集しましたら下記コマンドを実行してください。

php artisan migrate

モデルとコントローラーの作成

モデルの作成

下記のコマンドからコントローラーとモデルを作成します。

php artisan make:controller Auth/SocialLoginController
php artisan make:model SocialAccount

UserモデルとSocialAccountモデルを編集します。

SocialAccountモデルと紐付けます。

SocialAccountは一人のユーザーに対して複数あるとしています。

例えばFacebookやGoogleでもログインすることがあることを考慮してます。

app/User.php

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function socialAccounts(){
        return $this->hasMany(socialAccount::class);
    }
}
app/SocialAccount.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class SocialAccount extends Model
{
    protected $fillable = [
        'user_id', 'provider_name', 'provider_id'
    ];

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

コントローラーの編集

OAuthで利用するURLは外部サービスの認証ページにリダイレクトするURLと外部サービスからコールバックされるURLの2つです。

コールバック時にSocialiteのメソッドを介してユーザー情報を取得します。

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
use App\SocialAccount;
use App\User;
use Exception;

class SocialLoginController extends Controller
{
    // GitHubの認証ページヘユーザーを転送するためのルート
    public function redirectToProvider(String $provider)
    {
        return Socialite::driver($provider)->redirect();
    }

    // GitHubの認証後に戻るルート
    public function providerCallback(String $provider)
    {
        // エラーならwelcome pageに遷移
        try {
            $social_user = Socialite::with($provider)->user();
        } catch (Exception $e) {
            return redirect('/welcome');
        }

        // nameかnickNameをuserNameにする
        if ($social_user->getName()) {
            $user_name = $social_user->getName();
        } else {
            $user_name = $social_user->getNickName();
        }

        // userテーブルに保存
        $auth_user = User::firstOrCreate([
            'email' => $social_user->getEmail(), 
            'name' => $user_name
        ]);

        // social accountテーブルに保存
        $auth_user->socialAccounts()->firstOrCreate([
            'provider_id'=>$social_user->getId(),
            'provider_name'=>$provider
        ]);

        // ログイン
        auth()->login($auth_user);

        // homeページに転送
        return redirect()->to('/home'); 
    }
}

ルーティング設定

ルーティングを設定します。

// GitHubの認証後に戻るためのルーティング
Route::get('social-auth/{provider}/callback','Auth\SocialLoginController@providerCallback');
// GitHubの認証ベージに遷移するためのルーティング
Route::get('social-auth/{provider}','Auth\SocialLoginController@redirectToProvider')->name('social.redirect');

ユーザ登録からログイン認証画面の作成

Laravel/uiライブラリのインストール

laravel/uiライブラリをインストールします。

下記コマンドを実行してください。

composer require laravel/ui 1.*
php artisan ui vue --auth
npm install
npm run dev

ログイン画面にGitHubのリンクを追加

resources/view/auth/login.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ __('Login') }}</div>

                    <div class="card-body">
                        <form method="POST" action="{{ route('login') }}">
                            @csrf

                            {{-- githubへのリンク追加 --}}
                            <div class="form-group row">
                                <label for="name" class="col-md-4 col-form-label text-md-right">Login With</label>
                                <div class="col-md-6">
                                    <a href="{{ route('social.redirect', 'github') }}" class="btn btn-success">Github</a>
                                </div>
                            </div>
                            {{-- ここまで追加 --}}

                            <div class="form-group row">
                                <label for="email"
                                    class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                                <div class="col-md-6">
                                    <input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
                                        name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                    @error('email')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="password"
                                    class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                                <div class="col-md-6">
                                    <input id="password" type="password"
                                        class="form-control @error('password') is-invalid @enderror" name="password"
                                        required autocomplete="current-password">

                                    @error('password')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row">
                                <div class="col-md-6 offset-md-4">
                                    <div class="form-check">
                                        <input class="form-check-input" type="checkbox" name="remember" id="remember"
                                            {{ old('remember') ? 'checked' : '' }}>

                                        <label class="form-check-label" for="remember">
                                            {{ __('Remember Me') }}
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <div class="form-group row mb-0">
                                <div class="col-md-8 offset-md-4">
                                    <button type="submit" class="btn btn-primary">
                                        {{ __('Login') }}
                                    </button>

                                    @if (Route::has('password.request'))
                                        <a class="btn btn-link" href="{{ route('password.request') }}">
                                            {{ __('Forgot Your Password?') }}
                                        </a>
                                    @endif
                                </div>
                            </div>

                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

登録画面にGitHubのリンクを追加

resources/view/auth/register.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ __('Register') }}</div>

                    <div class="card-body">
                        <form method="POST" action="{{ route('register') }}">
                            @csrf

                            {{-- githubへのリンク追加 --}}
                            <div class="form-group row">
                                <label for="name" class="col-md-4 col-form-label text-md-right">Login With</label>
                                <div class="col-md-6">
                                    <a href="{{ route('social.redirect', 'github') }}" class="btn btn-success">Github</a>
                                </div>
                            </div>
                            {{-- ここまで追加 --}}

                            <div class="form-group row">
                                <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                                <div class="col-md-6">
                                    <input id="name" type="text" class="form-control @error('name') is-invalid @enderror"
                                        name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

                                    @error('name')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="email"
                                    class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                                <div class="col-md-6">
                                    <input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
                                        name="email" value="{{ old('email') }}" required autocomplete="email">

                                    @error('email')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="password"
                                    class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                                <div class="col-md-6">
                                    <input id="password" type="password"
                                        class="form-control @error('password') is-invalid @enderror" name="password"
                                        required autocomplete="new-password">

                                    @error('password')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="password-confirm"
                                    class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

                                <div class="col-md-6">
                                    <input id="password-confirm" type="password" class="form-control"
                                        name="password_confirmation" required autocomplete="new-password">
                                </div>
                            </div>

                            <div class="form-group row mb-0">
                                <div class="col-md-6 offset-md-4">
                                    <button type="submit" class="btn btn-primary">
                                        {{ __('Register') }}
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

完成

Githubのボタンを押すとログイン成功画面が出力されます。

image.png

image.png

8
2
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
8
2