【開発環境】supabaseマジックリンクでサインイン処理
投稿日: 2024年12月26日
以前、Google認証でログインする機能をご紹介しましたが、この方法で最近の宿題管理のアプリもログイン機能作りまして、ログイン処理ごのリダイレクト先がsupabaseで設定したSiteURLになってしまい、本番環境でもlocalhostにリダイレクトして動揺しました。
タマネギ先生は設定で上手いことリダイレクト出来たようなのですが、私がやるとどうしてもそうなってしまって困って、そもそもGCPとかsupabaseとかよくわからないので、ログイン機能を分けた方が私は早いかもしれないと考えて、ログイン機能を開発環境はで切り分けることにしました。
学習アプリではぶべさんはsupabaseのブランチ機能を利用して開発、本番でsiteURL を分けたそうなのですが、有料プランでのみ可能だそうです。
調査してあれこれ試した結果、マジックリンクで実装するのが簡単そうだったのでそうしてみました。
このページに書いてある通りにしました。
.env
NODE_ENV="development"
# テストに使うアカウントのメールアドレス
TEST_EMAIL="XXXXXX@gmail.com"
この環境変数は開発環境でしか使わないし誤って登録するとエラいことなので、本番環境(vercel)には登録していません。
タマネギ先生が共有して下さったのを参考にというかそのまま真似て、開発環境か否かだけを返すファイルも用意しました。
他でも使うかもしれないので・・
export const isDevelopmentEnv = process.env.NODE_ENV === "development";
"use client";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useSupabaseSession } from "./_hooks/useSupabaseSession";
import { supabase } from "./_utils/supabase";
import { Button } from "./_components/Button";
import { isDevelopmentEnv } from "./_utils/isDevelopmentEnv";
export default function Home() {
const router = useRouter();
const { session, isLoading } = useSupabaseSession();
useEffect(() => {
if (isLoading) return;
if (session) {
router.replace("/dashboard");
}
}, [session, isLoading, router]);
const signIn = async () => {
try {
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${process.env.NEXT_PUBLIC_APP_BASE_URL}/oauth/callback/google`,
},
});
if (error) throw new Error(error.message);
} catch (e) {
alert(`ログインに失敗しました:${e}`);
console.error(e);
}
};
const devSignIn = async () => {
try {
const response = await fetch("api/oauth/dev", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
if (response.status !== 200) {
const errorData = await response.json();
const errorMessage = errorData.message;
throw new Error(errorMessage);
}
alert("マジックリンク送信");
} catch (e) {
alert(`ログインに失敗しました:${e}`);
console.error(e);
}
};
return (
<div className="flex h-screen items-center justify-center gap-5">
<Button type="button" onClick={signIn} variant="bg-blue">
Googleアカウントでログイン
</Button>
{isDevelopmentEnv && (
<Button type="button" onClick={devSignIn} variant="bg-blue">
開発用ログイン
</Button>
)}
</div>
);
}
メール送信する処理はバックエンドで行うのでそのAPIを叩きます
const devSignIn = async () => {
try {
const response = await fetch("api/oauth/dev", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
if (response.status !== 200) {
const errorData = await response.json();
const errorMessage = errorData.message;
throw new Error(errorMessage);
}
alert("マジックリンク送信");
} catch (e) {
alert(`ログインに失敗しました:${e}`);
console.error(e);
}
};
開発環境の場合だけ専用ボタン表示します。
{isDevelopmentEnv && (
<Button type="button" onClick={devSignIn} variant="bg-blue">
ログイン
</Button>
)}
でも今これ見ながら開発環境の場合はこのボタンだけおけばよかったなと思いました。
いやむしろログインボタンは一つだけにして、isDevelopmentEnvで関数内の処理を切り分けたら良かった・・?
いやいや開発用のButtonコンポーネントを切り出してログイン処理まで持たせて別にするのが一番スマート・?
色々手段あるのでまたリファクタするときに考えますw
import { NextResponse } from "next/server";
import { buildError } from "@/app/api/_utils/buildError";
import { supabase } from "@/app/_utils/supabase";
export const POST = async () => {
const emailRedirectTo =
`${process.env.NEXT_PUBLIC_APP_BASE_URL}/dashboard` || "";
try {
const { error } = await supabase.auth.signInWithOtp({
email: process.env.TEST_EMAIL || "",
options: {
emailRedirectTo,
},
});
if (error) throw new Error(error.message);
return NextResponse.json(
{
message: "成功",
},
{ status: 200 }
);
} catch (e) {
return buildError(e);
}
};
今回は開発環境のログイン用のメールアドレスを環境変数に持たせているのでサインイン処理はバックエンドで行います。
どうせメール開かないとログインできないし、フロントで完結でもよかったのかな・・という気もしますが・・
これでログインボタン押すとこのAPI叩いてマジックリンクが添付されたメールが届きます!
文面などはsupabaseの管理画面で設定可能です
このリンク開くとemailRedirectToに指定したページが開いてログイン完了です!!
カリキュラムやオリアプ第一弾ではメール認証、先日がGoogle認証ときて今回はマジックリンク認証を実装しました!
色々扱うと本当に勉強になりますし、なにより楽しいです。
開発環境のみでしか使わないのでスピード命(いつも時短しか考えてないようなw)で実装しました。
これからこのログイン処理使ってまたアプリのブラッシュアップしていきたいと思います🎵
まだ何するか考えてないけど・・