supabaseのlocalとoauth(google)ができるまで。

対象はNext.js 15 app router、supabase
pnpm add supabase --save-dev

pnpx supabase init

pnpx supabase start

- config.tomlはlocalでしか使わない
- supabase initで作成される
- google oauthのclient idとかを記載
- ファイル内では envに記載したものをenv(GITHUB_CLIENT_ID)で参照可能

新しくClientを作成する
local用と本番用を作るがここではlocal用だけ

consoleに設定するのは
名前は適当、
originはhttp://localhost:3000、
callbackは http://localhost:54321/auth/v1/callback
対象のテストユーザは自分のgmailアカウント

scopeはDataAccesから設定する
.../auth/userinfo.email
...auth/userinfo.profile
openid

config.tomlに以下を設定
127.0.0.1とかいうループバックアドレス?とかではなくてlocalhostにした方がいいそうな。
それにまつわるzennの記事もあった。
authに
site_url = "http://localhost:3000"
additional_redirect_urls = ["http://localhost:3000"]
[auth.external.google]
enabled = true
client_id = "【ここにキー】"
secret = "【ここにシークレットキー】"
redirect_uri = "http://localhost:54321/auth/v1/callback"

pnpm add @supabase/ssr @supabase/supabase-js

https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
localhostのstudioから参照できるconnectの中にあるものはデマ。上のやつが正しい。
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
)
}
import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
return await updateSession(request)
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
const {
data: { user },
} = await supabase.auth.getUser()
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth')
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
// creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse
}

AIにlayout.tsxとlogin用のpage.tsxを作ってもらった。
"use client";
import React from "react";
import { handleGoogleLogin } from "@/lib/actions/auth";
export default function LoginPage() {
return (
<div className="flex flex-col items-center justify-center space-y-6">
<h1 className="text-2xl font-bold">ログイン</h1>
<button
className="flex items-center justify-center gap-2 px-4 py-2 bg-white text-gray-700 border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 transition-colors"
onClick={handleGoogleLogin}
>
<GoogleIcon />
<span>Googleでログイン</span>
</button>
</div>
);
}
// シンプルなGoogleアイコンコンポーネント
function GoogleIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
className="w-5 h-5"
>
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
);
}

server action
"use server";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export async function handleGoogleLogin() {
const supabase = await createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `http://localhost:3000/api/auth/callback`,
},
});
console.log(data);
if (data?.url) {
redirect(data.url);
} else {
console.error(error);
}
}

記載してないが、現状 .env.localには以下がある想定
記述は全部大文字だがめんどいので小文字にしている。
google_client_id,
google_client_secret
NEXT_PUBLIC_SUPABASE_URL!,
NEXT_PUBLIC_SUPABASE_ANON_KEY!,

これだけやって、localサーバ立ち上げて、googleログインボタン押したら
localhost:3000に遷移しているはず。何もしていないので今はnext.jsのlogoが見えるお馴染みのやつが見えるはず。またlocalのsupabase studioのAuthenticationを覗いてみると先ほどのgmailアカウントのレコードがinsertされているはず。

本番についてはsupabaseのdashboardのauthentication/providersのところからgoogleを見つけて、今度はgoogle consoleで本番用のclient_secretとかを作ってそこに記入すれば良い。

またauthが作られると同時にUserとしても作りたいとなったらPostgreSQLのtriggerとかを設定すればいける。それは今回は別であるのでやらないが、調べたら無限に出てくるので割愛。