LoginSignup
0
1

More than 1 year has passed since last update.

【AWS】LaravelアプリをEC2デプロイ⑦【API編】

Last updated at Posted at 2022-06-06

0. はじめに

大阪のLaravel初学者サウナーこと、kazumakishimoto(@kazuma_dev)です!
Route53の独自ドメイン登録に伴った、外部API(Google / Twitter)連携方法です(Laravel Socialite解説付き)。

0-1. 前回記事

  • 【AWS】LaravelアプリをEC2デプロイ【まとめ編】

  • 【AWS】LaravelアプリをEC2デプロイ①【CloudFormation / EC2 / RDS編】

  • 【AWS】LaravelアプリをEC2デプロイ②【Route53編】

  • 【AWS】LaravelアプリをEC2デプロイ③【ACM / ELB編】

  • 【AWS】LaravelアプリをEC2デプロイ④【CircleCI / CodeDeploy編】

  • 【AWS】LaravelアプリをEC2デプロイ⑤【SNS / Chatbot編】

  • 【AWS】LaravelアプリをEC2デプロイ⑥【S3編】

0-2. 全体の流れ

1.Google
2.Twitter
3.Laravel
補足
Reference
次回記事

0-3. 本記事の対象者

  • Laravelと外部API(Google / Twitter)連携したい方

0-4. 事前準備

  • Google / Twitterアカウント作成済み(※TwitterAPIは申請が必要)
  • grflhogeはサンプル名なので適宜変更して下さい

0-5. 要件

  • GoogleAPI連携
  • TwitterAPI連携
  • Laravel Socialite実装

0-6. 本番環境

ツール バージョン
OS Amazon Linux 2
nginx 1.12
PHP 7.4.28
Laravel 6.20.44
MySQL 5.7.37
Composer 1.10.26
Node.js 13.14.0

0-7. AWS構成図

aws

1. Google

1-1. プロジェクト作成

image.png
image.png

1-2. OAuth同意画面

image.png
image.png
image.png
image.png
image.png
image.png

1-3. OAuthクライアントID

image.png
image.png
image.png

2. Twitter

2-1. TwitterAPI申請

  • 下記記事を参照にTwitterAPI申請する

2-2. アプリ作成

  • APIキーやトークンは後ほど変わるためコピーせず進む
    image.png
    image.png

2-3. OAuth設定

image.png
image.png

2-4. APIキー/トークン発行

  • .envファイルに記載するためコピーしておく
    image.png

3. Laravel

3-1. Laravel Socialite

local
$ docker-compose exec app bash
# COMPOSER_MEMORY_LIMIT=-1 composer require laravel/socialite
Package manifest generated successfully.
# exit

3-2. config

3-2-1. app.php

config/app.php
'providers' => [ 
    // 略
    Laravel\Socialite\SocialiteServiceProvider::class,
],
'aliases' => [
    // 略
    'Socialite' => Laravel\Socialite\Facades\Socialite::class,
]

3-2-2. services.php

config/services.php
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('APP_URL') . '/login/google/callback',
    ],

    'twitter' => [
        'client_id' => env('TWITTER_CLIENT_ID'),
        'client_secret' => env('TWITTER_CLIENT_SECRET'),
        'redirect' => env('APP_URL') . '/login/twitter/callback',
    ],

3-3. Route

web.php
# SNSアカウントログイン
Route::prefix('login')->name('login.')->group(function () {
    Route::get('/{provider}', 'Auth\LoginController@redirectToProvider')->name('{provider}');
    Route::get('/{provider}/callback', 'Auth\LoginController@handleProviderCallback')->name('{provider}.callback');
});
# SNSアカウントユーザー登録
Route::prefix('register')->name('register.')->group(function () {
    Route::get('/{provider}', 'Auth\RegisterController@showProviderUserRegistrationForm')->name('{provider}');
    Route::post('/{provider}', 'Auth\RegisterController@registerProviderUser')->name('{provider}');
});

3-4. Migration

database/migrations/2014_10_12_000000_create_users_table.php
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('twitter_id')->nullable();
            $table->string('name')->unique();
            $table->string('age')->nullable();
            $table->string('gender')->nullable();
            $table->string('avatar')->nullable();
            $table->text('introduction')->nullable();
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }

3-5. Model

app/Models/User.php
    protected $fillable = [
        'twitter_id', 'name', 'age', 'gender', 'avatar', 'introduction', 'email', 'password',
    ];

3-6. Controller

3-6-1. LoginController.php

app/Http/Controllers/Auth/LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    // ゲストユーザー用のユーザーIDを定数として定義
    private const GUEST_USER_ID = 1;

    // ゲストログイン処理
    public function guestLogin()
    {
        // id=1 のゲストユーザー情報がDBに存在すれば、ゲストログインする
        if (Auth::loginUsingId(self::GUEST_USER_ID)) {
            return redirect('/');
        }

        return redirect('/');
    }

    // SNS認証ページへユーザーをリダイレクト
    public function redirectToProvider(string $provider)
    {
        return Socialite::driver($provider)->redirect();
    }

    // ログイン
    public function handleProviderCallback(Request $request, string $provider)
    {
        // 認証結果の受け取り
        $providerUser = Socialite::driver($provider)->user();

        // Google
        if ($provider === 'google') {
            // Googleから取得したユーザー情報からメールアドレスを取得
            $user = User::where('email', $providerUser->getEmail())->first();

        // Twitter
        } elseif ($provider === 'twitter') {
            // Twitterから取得したユーザー情報からユーザーIDを取得
            $user = User::where('twitter_id', $providerUser->getId())->first();
        }

        // ログイン処理
        if ($user) {
            $this->guard()->login($user, true);
            return $this->sendLoginResponse($request);
        }

        // Google
        if ($provider === 'google') {

            $data = [
                'provider' => $provider,
                'email' => $providerUser->getEmail(),
                'token' => $providerUser->token,
            ];

            return redirect()->route('register.{provider}', $data);

        // Twitter
        } elseif ($provider === 'twitter') {

            $data = [
                'provider' => $provider,
                'twitter_id' => $providerUser->getId(),
                'token' => $providerUser->token,
                'tokenSecret' => $providerUser->tokenSecret,
            ];
            // DBにユーザー情報がなければ作成する
            return redirect()->route('register.{provider}', $data);
        }
    }
}

3-6-2. RegisterController.php

app/Http/Controllers/Auth/RegisterController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Socialite\Facades\Socialite;
use App\Rules\CustomPasswordValidation;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        $messages = [
            'regex' => ':attributeに「/」と半角スペースは使用できません。',
        ];

        return Validator::make($data, [
            'name' => ['required', 'regex:/^(?!.*\s).+$/u', 'regex:/^(?!.*\/).*$/', 'max:15', 'unique:users'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', new CustomPasswordValidation, 'confirmed'],
        ],
        $messages
    );
    }

    public function showProviderUserRegistrationForm(Request $request, string $provider)
    {
        $token = $request->token;

        $providerUser = Socialite::driver($provider);

        // Google
        if ($provider === 'google') {
            $providerUser = $providerUser->userFromToken($token);

            $data = [
                'provider' => $provider,
                'email' => $providerUser->getEmail(),
                'token' => $providerUser->token,
            ];

            return view('auth.social_register', $data);

        // Twitter
        } elseif ($provider === 'twitter') {
            $tokenSecret = $request->tokenSecret;
            $providerUser = $providerUser->userFromTokenAndSecret($token, $tokenSecret);

            $data = [
                'provider' => $provider,
                'twitter_id' => $providerUser->getId(),
                'token' => $providerUser->token,
                'tokenSecret' => $providerUser->tokenSecret,
            ];

            return view('auth.social_register', $data);
        }
    }

    public function registerProviderUser(Request $request, string $provider)
    {
        // Google
        if ($provider === 'google') {
            $request->validate([
                'name' => ['required', 'string', 'min:1', 'max:15', 'unique:users'],
                'token' => ['required', 'string'],
            ]);

        // Twitter
        } elseif ($provider === 'twitter') {
            $request->validate([
                'name' => ['required', 'string', 'min:1', 'max:15', 'unique:users'],
                'token' => ['required', 'string'],
                'tokenSecret' => ['required', 'string'],
            ]);
        }

        $token = $request->token;

        $providerUser = Socialite::driver($provider);

        // Google
        if ($provider === 'google') {
            $providerUser = $providerUser->userFromToken($token);

        // Twitter
        } elseif ($provider === 'twitter') {
            $tokenSecret = $request->tokenSecret;
            $providerUser = $providerUser->userFromTokenAndSecret($token, $tokenSecret);
        }

        // Google
        if ($provider === 'google') {
            $user = User::create([
                'name' => $request->name,
                'avatar' => asset(config('user.avatar_path.default')),
                'email' => $providerUser->getEmail(),
                'password' => null,
            ]);

        // Twitter
        } elseif ($provider === 'twitter') {
            $user = User::create([
                'name' => $request->name,
                'avatar' => asset(config('user.avatar_path.default')),
                'email' => $request->email,
                'twitter_id' => $providerUser->getId(),
                'password' => null,
            ]);
        }

        $this->guard()->login($user, true);

        return $this->registered($request, $user)
        ?: redirect($this->redirectPath());
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'avatar' => asset(config('user.avatar_path.default')),
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

3-7. View

3-7-1. login.blade.php

resources/views/auth/login.blade.php
<div>
    <a href="{{ route('login.{provider}', ['provider' => 'google']) }}" class="btn btn-block btn-danger mb-4 col-lg-8 col-md-9 col-sm-10 col-xs-12"><i class="fab fa-google mr-1"></i>Googleでログイン</a>
</div>
<div>
    <a href="{{ route('login.{provider}', ['provider' => 'twitter']) }}" class="btn btn-block btn-info mb-4 col-lg-8 col-md-9 col-sm-10 col-xs-12"><i class="fab fa-twitter mr-1"></i>Twitterでログイン</a>
</div>

3-7-2. register.blade.php

resources/views/auth/register.blade.php
<div>
    <a href="{{ route('login.{provider}', ['provider' => 'google']) }}" class="btn btn-block btn-danger mb-4"><i class="fab fa-google mr-1"></i>Googleで登録</a>
</div>
<div>
    <a href="{{ route('login.{provider}', ['provider' => 'twitter']) }}" class="btn btn-block btn-info mb-4"><i class="fab fa-twitter mr-1"></i>Twitterで登録</a>
</div>

3-7-3. social_register.blade.php

resources/views/auth/social_register.blade.php
@extends('app')

@section('title', 'ユーザー登録 - grfl')

@section('content')

@include('nav')
<main>
    <div class="container">
        <div class="row">
            <div class="mx-auto col col-12 col-sm-11 col-md-9 col-lg-7 col-xl-6">
                <div class="card mt-3">
                    <div class="card-body text-center">
                        <h2 class="h3 card-title text-center mt-2">ユーザー登録</h2>
                        @include('error_card_list')
                        <div class="card-text">
                            <form method="POST" action="{{ route('register.{provider}', ['provider' => $provider]) }}">
                                @csrf
                                <input type="hidden" name="token" value="{{ $token }}">
                                @if ($provider === 'twitter')
                                <input type="hidden" name='tokenSecret' value="{{ $tokenSecret }}">
                                @endif
                                <div class="md-form">
                                    <label for="name">ユーザー名</label>
                                    <input class="form-control" type="text" id="name" name="name" required>
                                    <small>英数字316文字(登録後の変更はできません)</small>
                                </div>
                                <div class="md-form">
                                    <label for="email">メールアドレス</label>
                                    @if ($provider === 'google')
                                    <input class="form-control" type="text" id="email" name="email" value="{{ $email }}" disabled>
                                    @elseif ($provider === 'twitter')
                                    <input class="form-control" type="text" id="email" name="email" required>
                                    @endif
                                </div>
                                <button class="btn btn-block blue-gradient mt-2 mb-2" type="submit">ユーザー登録</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</main>

@include('footer')

@endsection

3-8. 環境変数

ec2-user
$ ssh ec2-user@xx.xxx.xxx.xxx -i ~/.ssh/hoge.pem
$ cd /var/www/grfl/src
$ sudo vi .env
.env
# Google Auth
+ GOOGLE_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
+ GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx

# Twitter Auth
+ TWITTER_CLIENT_ID=TwitterAPI Key
+ TWITTER_CLIENT_SECRET=TwitterAPI Secret Key
+ TWITTER_CALLBACKURL=https://grfl.work/login/twitter/callback
ec2-user
$ php artisan config:cache

3-9. 動作確認

image.png
image.png
image.png

補足

開発環境(FW/ツールのバージョンなど)

ツール バージョン
Vue.js 2.6.14
jQuery 3.4.1
PHP 7.4.1
Laravel 6.20.43
MySQL 5.7.36
Nginx 1.18.0
Composer 2.0.14
npm 6.14.6
Git 2.33.1
Docker 20.10.11
docker-compose v2.2.1
PHPUnit 8.0
CircleCI 2.1
heroku 7.59.4
MacBook Air M1,2020
macOS Monterey 12.3
Homebrew 3.3.8

ディレクトリ構造

【ルートディレクトリ】
├─ .circleci
│   └─ config.yml
├─ aws / CloudFormation
│   └─ ec2.yml
├─ docker
│   └─ mysql
│   └─ nginx
│   └─ php
│   └─ phpmyadmin
├─ src
│   └─ 【Laravelのパッケージ】
└─ docker-compose.yml

Reference

次回記事

  • 【AWS】お役立ちリンク集【随時更新】

0
1
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
0
1