【Laravel + React】SPAアプリでのSNS認証を簡単に実装してみた
こんにちは。あっきーです。
シェアハウスに住んでいるんですが、北側と南側で部屋の温度が5度ほど違うと実感した、フリーランスエンジニアです。
今回は、LaravelとReactを使ったアプリでのSNS認証を行っていきたいと思います。
ほとんどのアプリではアカウント作成やログインにTwitterなどを使用できますね。
あれを自作でやってみようという話です。それではやっていきましょう。
なお、今回は【React + Laravel】SPAで認証機能と権限管理(fortify+sanctum)
で利用したコードを使いまわしたいので、模写したい場合はこちらも合わせてみてください。
今回の環境は以下の通り。(2021年11月現在で最新)
- Laravel : 8
- react : 17
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をLaravelで作ってみた【システム解説あり】