【開発環境】supabaseマジックリンクでサインイン処理

【開発環境】supabaseマジックリンクでサインイン処理

投稿日: 2024年12月26日

学習振り返り
要約
  • 開発環境でのログイン処理としてマジックリンクを使用することに決め、Supabaseを利用して実装した。
  • 環境変数で開発用のメールアドレスを設定し、フロントエンドからAPIを叩く形でメールを送信することにした。
  • ログイン機能の実装を通じてさまざまな認証方法を学び、今後のアプリのブラッシュアップに活用する予定。

はじめに

以前、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の管理画面で設定可能です

【開発用環境専用】マジックリンクでログイン|ShiftBブログ

このリンク開くとemailRedirectToに指定したページが開いてログイン完了です!!

おわりに

カリキュラムやオリアプ第一弾ではメール認証、先日がGoogle認証ときて今回はマジックリンク認証を実装しました!

色々扱うと本当に勉強になりますし、なにより楽しいです。

開発環境のみでしか使わないのでスピード命(いつも時短しか考えてないようなw)で実装しました。

これからこのログイン処理使ってまたアプリのブラッシュアップしていきたいと思います🎵

まだ何するか考えてないけど・・

シェア!

Threads
user
吉本茜
山口在住/二児の母/育休中
Loading...
記事一覧に戻る
Threads
0