Supabase Authで実装するユーザー認証 完全ガイド【2026年版】
Supabase Authとは
Supabase Authは、Supabaseが提供する認証サービスです。Firebase Authenticationの代替として設計されており、オープンソースで完全にセルフホスト可能です。
2026年現在、多くのスタートアップやSaaSプロダクトで採用されており、特にNext.js、Remix、SvelteKitなどのモダンなフレームワークとの相性が抜群です。
主な機能
- メール/パスワード認証
- OAuth(Google、GitHub、GitLab、Bitbucket等)
- Magic Link(パスワードレス認証)
- 電話番号認証(SMS)
- Row Level Security(RLS)による行レベルのアクセス制御
- JWT トークンベースのセッション管理
プロジェクトのセットアップ
Supabaseプロジェクトの作成
- Supabaseでアカウント作成
- 新しいプロジェクトを作成
- Project URLとanon keyを取得
ローカル環境のセットアップ
# Next.jsプロジェクトの作成
npx create-next-app@latest my-app --typescript --tailwind --app
# Supabaseクライアントのインストール
cd my-app
npm install @supabase/supabase-js @supabase/auth-helpers-nextjs
環境変数の設定
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Supabaseクライアントの初期化
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
メール/パスワード認証の実装
サインアップ
// app/signup/page.tsx
'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabase';
import { useRouter } from 'next/navigation';
export default function SignUp() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) {
setError(error.message);
return;
}
// 確認メールを送信した旨を表示
alert('確認メールを送信しました。メールをご確認ください。');
router.push('/login');
};
return (
<form onSubmit={handleSignUp} className="max-w-md mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">新規登録</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<input
type="email"
placeholder="メールアドレス"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded mb-4"
required
/>
<input
type="password"
placeholder="パスワード(8文字以上)"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 border rounded mb-4"
minLength={8}
required
/>
<button
type="submit"
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
>
登録
</button>
</form>
);
}
ログイン
// app/login/page.tsx
'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabase';
import { useRouter } from 'next/navigation';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
return;
}
router.push('/dashboard');
};
return (
<form onSubmit={handleLogin} className="max-w-md mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">ログイン</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<input
type="email"
placeholder="メールアドレス"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded mb-4"
required
/>
<input
type="password"
placeholder="パスワード"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 border rounded mb-4"
required
/>
<button
type="submit"
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
>
ログイン
</button>
</form>
);
}
ログアウト
// components/LogoutButton.tsx
'use client';
import { supabase } from '@/lib/supabase';
import { useRouter } from 'next/navigation';
export default function LogoutButton() {
const router = useRouter();
const handleLogout = async () => {
await supabase.auth.signOut();
router.push('/login');
};
return (
<button
onClick={handleLogout}
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
>
ログアウト
</button>
);
}
OAuth認証の実装
Google認証
Supabaseダッシュボードで設定:
- Authentication > Providers > Google
- Google Cloud ConsoleでOAuth 2.0クライアントIDを作成
- Client IDとClient Secretを入力
// components/GoogleLoginButton.tsx
'use client';
import { supabase } from '@/lib/supabase';
export default function GoogleLoginButton() {
const handleGoogleLogin = async () => {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent',
},
},
});
if (error) {
console.error('Google login error:', error);
}
};
return (
<button
onClick={handleGoogleLogin}
className="w-full bg-white border border-gray-300 text-gray-700 p-2 rounded hover:bg-gray-50 flex items-center justify-center gap-2"
>
<img src="/google-icon.svg" alt="Google" className="w-5 h-5" />
Googleでログイン
</button>
);
}
GitHub認証
Supabaseダッシュボードで設定:
- Authentication > Providers > GitHub
- GitHubでOAuth Appを作成
- Client IDとClient Secretを入力
// components/GitHubLoginButton.tsx
'use client';
import { supabase } from '@/lib/supabase';
export default function GitHubLoginButton() {
const handleGitHubLogin = async () => {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) {
console.error('GitHub login error:', error);
}
};
return (
<button
onClick={handleGitHubLogin}
className="w-full bg-gray-900 text-white p-2 rounded hover:bg-gray-800"
>
GitHubでログイン
</button>
);
}
コールバックハンドラー
// app/auth/callback/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get('code');
if (code) {
const supabase = createRouteHandlerClient({ cookies });
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(`${requestUrl.origin}/dashboard`);
}
Row Level Security(RLS)の設定
ユーザープロファイルテーブルの作成
-- Supabase SQL Editor で実行
create table public.profiles (
id uuid references auth.users on delete cascade primary key,
username text unique,
avatar_url text,
created_at timestamp with time zone default now()
);
-- RLSを有効化
alter table public.profiles enable row level security;
-- 自分のプロファイルのみ閲覧可能
create policy "Users can view own profile"
on public.profiles
for select
using (auth.uid() = id);
-- 自分のプロファイルのみ更新可能
create policy "Users can update own profile"
on public.profiles
for update
using (auth.uid() = id);
-- サインアップ時に自動でプロファイルを作成
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, username)
values (new.id, new.email);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
プロファイルの取得と更新
// lib/profile.ts
import { supabase } from './supabase';
export async function getProfile(userId: string) {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single();
if (error) throw error;
return data;
}
export async function updateProfile(userId: string, updates: any) {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', userId);
if (error) throw error;
return data;
}
セッション管理
認証状態の監視
// components/AuthProvider.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';
import { User } from '@supabase/supabase-js';
const AuthContext = createContext<{ user: User | null }>({ user: null });
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// 初期セッションの取得
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
});
// セッション変更の監視
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => subscription.unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);
保護されたページの実装
// app/dashboard/page.tsx
'use client';
import { useAuth } from '@/components/AuthProvider';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function Dashboard() {
const { user } = useAuth();
const router = useRouter();
useEffect(() => {
if (!user) {
router.push('/login');
}
}, [user, router]);
if (!user) {
return <div>読み込み中...</div>;
}
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">ダッシュボード</h1>
<p>ようこそ、{user.email}さん</p>
</div>
);
}
まとめ
Supabase Authは、モダンなWeb アプリケーションに必要な認証機能を簡単に実装できる優れたサービスです。特に以下の点で優れています。
- セットアップが非常に簡単
- Row Level Securityによる強固なセキュリティ
- 主要なOAuthプロバイダーのサポート
- Next.jsなどのフレームワークとの統合が容易
まずは小規模なプロジェクトで試し、認証フローを理解してから本格的に導入することをお勧めします。