search
LoginSignup
1

posted at

updated at

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

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】お役立ちリンク集【随時更新】

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
What you can do with signing up
1