【Laravel + React】SPAアプリでのSNS認証を簡単に実装してみた

Blog

こんにちは。あっきーです。
シェアハウスに住んでいるんですが、北側と南側で部屋の温度が5度ほど違うと実感した、フリーランスエンジニアです。

今回は、LaravelとReactを使ったアプリでのSNS認証を行っていきたいと思います。
ほとんどのアプリではアカウント作成やログインにTwitterなどを使用できますね。

あれを自作でやってみようという話です。それではやっていきましょう。
なお、今回は【React + Laravel】SPAで認証機能と権限管理(fortify+sanctum)
で利用したコードを使いまわしたいので、模写したい場合はこちらも合わせてみてください。

今回の環境は以下の通り。(2021年11月現在で最新)

  • Laravel : 8
  • react : 17
ここではTwitter認証を行うんですが、ローカル環境だとうまくいかないのでドメインを取得してやってみてください!

SNS認証用のパッケージSocialiteをインストール

LaravelにはSNS認証用のパッケージがあります(さすが!)
composerよりインストールしましょう。

composer require laravel/socialite

Twitterのアクセスキーを取得

今回はTwitterの認証をやっていきます。

まずはツイッターのDeveloper Platformにアクセスします。
アカウントを作成して管理画面に行きましょう。

「Projects & Apps -> OverView」に行き、「Create App」をクリックしましょう!

アプリ名などの情報を入れていくと、アプリのアクセスキーとシークレットキーが取得できるので、メモしましょう。
.envファイルを開き以下のように記述してください。


TWITTER_CLIENT_ID=アクセスキー
TWITTER_CLIENT_SECRET=シークレットキー

ツイッタープラットフォームに戻り、先ほど作成したアプリの管理画面に行き、「Authentication settings」の編集を行います。

各URLのexample.comには自分のドメインを入れてください!

Socialiteのインストールと設定

LaravelにはSocialiteというSNS認証用パッケージがあるので、これを使わせていただきましょう!

以下のコマンドを実行してインストールします。

composer require laravel/socialite

config/services.phpに以下を追記します。


'twitter' => [
  'client_id' => env('TWITTER_CLIENT_ID'),
  'client_secret' => env('TWITTER_CLIENT_SECRET'),
  'redirect' => 'https://example.com/login/twitter/callback', //example.comは自分のドメイン
],

SNS認証を有効化する(サーバー側)

ここから、SNS認証を行えるようにコードを書いていきます。
まずはサーバー側です。

認証用テーブルを作成

userテーブルに項目を追加して、そこに認証データを追記していくのも良いんですが、SNS認証データは別にテーブルを作成して保存した方が管理がしやすいです。

そのテーブルをユーザーテーブルと紐づければ問題なしです。

というわけで、コマンドでマイグレーションファイルを作成し、以下のように編集します。

php artisan make:migration create_identity_providers_table

Schema::create('identity_providers', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('provider_name');
    $table->string('provider_user_id');
    $table->timestamps();

    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});

ユーザーIDとSNSプロバイダ名とそのIDを格納します。
このユーザーIDがusersテーブルのidと紐づけられるので認証が可能になります。

以下のコマンドを実行して、データベースにテーブルを作成しましょう。

php artisan migrate

認証モデルを作成

次はモデルです。以下のコマンドで作成して、

php artisan make:model IdentityProvider

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

class IdentityProvider extends Model
{
    use HasFactory;

    //以下追記
    protected $fillable = ['user_id', 'provider_name', 'provider_user_id'];

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

Userモデルとリレーションをつけたいのでそのメソッドも追記しています。
Userモデルにも追記しておきましょう。


public function identityProviders() {
  return $this->hasMany(IdentityProvider::class);
}

これは「1 : 多」のリレーションとなっています。ユーザー1人に対してFacebook, Twitterなど複数のSNSを持っているからです。
今回はTwitter認証のみしか行いませんが、今後の派生も考えてこの1対多の形にしています。

認証用コントローラーを作成

コントローラーです。同じように作成し編集していきます。

php artisan make:controller IdentityProviderController 

// クラスの中身
public function getProviderOAuthURL(string $provider) {
  $redirectUrl = Socialite::driver($provider)->redirect()->getTargetUrl();
  return response()->json([
    'redirect_url' => $redirectUrl,
  ]);
}

public function handleProviderCallback(string $provider) {
  $provider_user = Socialite::driver($provider)->user();
  $identity_provider = IdentityProvider::where('provider_user_id', $provider_user->getId())->first();
  if($identity_provider) { //login
    Auth::loginUsingId($identity_provider->user_id);
  } else { //create identity provider
    $user = User::where('email', $provider_user->getEmail())->first();
    
    if(!$user) {
      $user = User::create([
        'name' => ($provider_user->getName()) ? $provider_user->getName() : $provider_user->getNickname(),
        'email' => $provider_user->getEmail(),
        'photoURL' => $provider_user->getAvatar(),
      ]);
      $user->email_verified_at = Carbon::now();
      $user->save();
    }

    IdentityProvider::create([
      'user_id' => $user->id,
      'provider_name' => $provider,
      'provider_user_id' => $provider_user->getId(),
    ]);

    Auth::loginUsingId($user->id);
  }
  return true;
}

SNS認証の処理は2ステップでできています。

  • SNS情報を取得 -> getProviderOAuthURL
  • 取得したデータを保存 handleProviderCallback

getProviderOAuthURLでは、SNS認証ページのURLを返すだけのメソッドです。
(このURLにreact.jsからリダイレクトします)

handleProviderCallbackでは取得したデータを元に、認証をログインやアカウント作成を行っています。
すでに認証済みだったらログインし(ifの中)、そうでない場合は登録してログインしています(elseの中)。

APIルートを追加する

最後に、先ほどのコントローラーを呼び出すようにAPIルートを設定しましょう。


Route::get('/login/{provider}', [OAuthController::class, 'getProviderOAuthURL']);
Route::post('/login/{provider}/callback', [OAuthController::class, 'handleProviderCallback']);

SNS認証を有効化する(フロント側)

次はReact.jsでこのAPIを呼び出して認証をします。

views/Register.tsx, views/Login.tsx


const Register = () => {
  const socialLogin = (e: React.MouseEvent<HTMLElement>) => {
    setLoading(true)
    const provider = (e.target as HTMLButtonElement).value
    if(provider == 'twitter') {
      axios.get(`/api/login/${provider}`).then((res) => {
        console.log(res);
        window.location.href = res.data.redirect_url;
      })
    }
  }
}

return (
  <div className="text-center py-4">
     <LoadingButton loading={loading} onClick={socialLogin} variant="contained"><TwitterIcon/> Twitterでログイン</LoadingButton>
  </div>
)

上記では、「Twitterでログイン」を押すと、SNS認証用のリダイレクトURLが返ってくるので、そこにアクセスしています。
SPAアプリではあるんですが、SNS認証には一度外部のサイトに移動する必要があるので、普通にwindow.location.hrefでアクセスしています。

アクセスすると良くあるTwitterアカウントの確認等が行われて、認証ができます。

Twitter側からリダイレクトされるページを作成

上記で認証後、最初に設定したhttps://example.com/login/twitter/callbackにリダイレクトされるので、これ用のページを作成しましょう。

views/Callback.tsx


import axios from "axios";
import React from "react";
import CircularProgress from '@mui/material/CircularProgress';
import { useHistory, useLocation } from "react-router";
import { useAuth } from "../../components/AuthContext";

const Callback = () => {
  const token = useLocation().search;
  const auth = useAuth();
  const history = useHistory();
  auth?.signinWithProvider(token).then(() => { //この関数は後程作成
    history.push('/home');
  })

  return (
    <div className="p-4">
      <p>認証中...<CircularProgress/></p>
    </div>
  )
}

export default Callback

ここは単純に、さっき設定したAPI/login/{provider}/callbackを呼んでログインをするだけです。
APIをこのファイルで読んでも良いんですが
前の記事でAuthContext.tsxに認証処理をまとめていたので、こちらsigninWithProviderという関数で呼び出した方が何かと都合がいいのでこちらを採用します。

AuthContext.tsx


interface authProps {
  user: User | 'unauthorized' | 'unverified';
  register: (registerData: RegisterData) => Promise<void>
  signin: (loginData: LoginData) => Promise<void>;
  signinWithProvider: (token: string) => Promise<void>; // <- 追加
  signout: () => Promise<void>;
  saveProfile: (formData: FormData | ProfileData) => Promise<void | AxiosResponse<any>>;
}

const useProvideAuth = () => {
// 以下追加
  const signinWithProvider = (token: string) => {
    return axios.post(`/api/login/twitter/callback${token}`, {}).then(() => {
      axios.get('/api/user').then((res) => {
        setUser(res.data);
      })
    })
  }
  return {
    user,
    register,
    signin,
    signinWithProvider,
    signout,
    saveProfile
  }
}

最後にApp.tsxでルーティングを設定して完了です。


const App = () => {
    return (
      <ProvideAuth>
        <BrowserRouter>
          <div>
            <Switch>
              <Layout>
                <Route path="/login/:provider/callback" component={Callback} /> // <- 追加
              </Layout>
            </Switch>
          </div>
        </BrowserRouter>
      </ProvideAuth>
    )
}

これでTwitter認証ができました。

実際に本番環境で試してみてください!

以上です。それでは、また。

スポンサードサーチ

オススメ英語学習用SNS "Our Dictionary"

人気記事英語学習用SNSをLaravelで作ってみた【システム解説あり】