🫑

Next.js Supabase Github SignIn

2024/12/06に公開

Next.jsでGithub SignIn

Next.jsで、SupabaseのAuthを使用してGithub SignInを実装してみたが詰まるところが多かったので、どのように作ったか記事に書いておきます。

以前、Reactで作ってみたものをNext.jsでもやってみたいと試してみました。手順は同じ。
https://zenn.dev/joo_hashi/articles/580a8390030a6e

参考にした記事
https://qiita.com/KaitoMuraoka/items/94d6c227a0abb5bfcc57
https://qiita.com/maaaashi/items/05b4226f228a31088550


完成品

DEMO
何度か連携したのでGithubと連携する画面が表示されませんでした🙇
最初は許可するか表示されます。

https://youtu.be/GPFOfvcIWhA

  • 技術構成
  • next: 15.0.4
  • shadcn/ui
  • react-icons
  • supabase.js

packageの追加と環境構築は、bunを使用しております。

UIを少しだけかっこよくしたかったのでライブラリを追加しました。
https://ui.shadcn.com/docs/installation/next
https://react-icons.github.io/react-icons/

やること

プロジェクトを作成したら必要なパッケージを追加する
Supabaseのメールアドレス&パスワードログインの設定をしておく
.env.localにURL, anon keyの設定をする。

NEXT_PUBLIC_SUPABASE_URL=https://*************.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6*****************

パッケージを追加する。

bun add @supabase/auth-helpers-nextjs @supabase/supabase-js
bun add @supabase/ssr

shadcn/uiを追加

ボタンのコンポーネントしか使っておりませんが、shadcn/uiを使うとTailWindCSSを使用しなくてもかっこいいパーツを最初から使うことができます。

ここは好みの問題かな。自作する人もいますね。

bunx --bun shadcn@latest init

Buttonを追加:

bunx --bun shadcn@latest add button

react-iconsも追加しておく

install react-icons --save

作ってみよう

フォルダ構成はシンプルにしました。Githubのサンプルを参考に作ってみてください。

componentsディレクトリにボタンのコンポーネントを作成する。

Login Button

'use client'

import { Button } from '@/components/ui/button'
import { Github } from 'lucide-react'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { useState } from 'react'

export function GithubLoginButton() {
  const [isLoading, setIsLoading] = useState(false)
  const supabase = createClientComponentClient()

  const handleGithubLogin = async () => {
    try {
      setIsLoading(true)
      const { error } = await supabase.auth.signInWithOAuth({
        provider: 'github',
        options: {
          redirectTo: `${location.origin}/auth/callback`
        }
      })
      
      if (error) {
        throw error
      }
    } catch (error) {
      console.error('Error:', error)
      alert('ログインに失敗しました。もう一度お試しください。')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <Button
      onClick={handleGithubLogin}
      disabled={isLoading}
      className="w-full flex items-center justify-center gap-2"
    >
      <Github className="w-5 h-5" />
      {isLoading ? 'Signing in...' : 'Sign in with Github'}
    </Button>
  )
}

Logout Button

'use client'

import { Button } from '@/components/ui/button'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { useRouter } from 'next/navigation'

export function LogoutButton() {
  const router = useRouter()
  const supabase = createClientComponentClient()

  const handleLogout = async () => {
    await supabase.auth.signOut()
    router.refresh()
  }

  return (
    <Button
      onClick={handleLogout}
      variant="outline"
    >
      ログアウト
    </Button>
  )
}

UIを作成する。ログイン後に表示するページを作成する。

import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { LogoutButton } from '@/components/LogoutButton'

export default async function DashboardPage() {
  const supabase = createServerComponentClient({ cookies })
  
  const {
    data: { session },
  } = await supabase.auth.getSession()

  return (
    <div className="min-h-screen p-8">
      <div className="max-w-4xl mx-auto">
        <div className="flex justify-between items-center mb-8">
          <h1 className="text-2xl font-bold">
            ようこそ、{session?.user.user_metadata.full_name || 'ユーザー'}さん!
          </h1>
          <LogoutButton />
        </div>
      </div>
    </div>
  )
}

ログインをするページを作成する。ボタンのコンポーネントはこのページで使用する。

import Image from 'next/image'
import { GithubLoginButton } from '@/components/GithubLoginButton'

export default function LoginPage() {
  return (
    <div className="min-h-screen flex flex-col items-center justify-center bg-gray-50">
      <div className="max-w-md w-full space-y-8">
        <div className="flex flex-col items-center">
          <h1 className="text-2xl font-bold mb-4">Github Auth</h1>
          <GithubLoginButton />
        </div>
      </div>
    </div>
  )
}

ログインページを最初に表示してもよかったですが、会員サイトを見るとログインボタンを押すとログインページへ行くことがあるので、リンクをつけたページを作成いたしました。

import Link from "next/link";

export default function Home() {
  return (
    <div>
      <h1>Wel Come!</h1>
      <Link href="/login">Click to Login Page</Link>
    </div>
  );
}

UIを作成したら、ルートとミドルウェアの設定をするファイルを作成する。認証が通っていると遷移できるページを指定したりログアウトすると最初のページにリダイレクトさせることができるようになります。以前はこんなものを使っていなかった💦
middleware.tsを使ってみたいというのもあり今回試してみました。

auth/callback/にルートのファイルを作成する。

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`)
}

参考になりそうな情報がなくて詰まっていたところなのですが、認証が通っていなくてもページ遷移したり認証が通っていたら遷移する設定をするファイルがこちらのミドルウェアの設定ファイルです。

公式の解説

import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })

  const {
    data: { session },
  } = await supabase.auth.getSession()

  // Skip auth check for public routes
  if (req.nextUrl.pathname.startsWith('/auth/')) {
    return res
  }

  // ログインしていない場合、ログインページにリダイレクト
  if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {
    const redirectUrl = req.nextUrl.clone()
    redirectUrl.pathname = '/login'
    return NextResponse.redirect(redirectUrl)
  }

  // ログイン済みの場合、ログインページにアクセスするとダッシュボードにリダイレクト
  if (session && (req.nextUrl.pathname === '/login' || req.nextUrl.pathname === '/')) {
    const redirectUrl = req.nextUrl.clone()
    redirectUrl.pathname = '/dashboard'
    return NextResponse.redirect(redirectUrl)
  }

  return res
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
}

最後に

今回は、Next.js + Supabase + Github SignInの実装をやってみました。昔だとFirebaseを使うことが多かったですが、現在はSupabaseを使用した認証機能を実装する方法を使った方が楽にできたりすることもありますし、RDBが使えるのでCloud Firestore使いづらいなって人にはありがたいサービスです。

枯れた技術であるSQLの知識が活かせるので学習もしやすく、NoSQLが弱い検索機能も強いのでおすすめです。とはいえモバイルとかとなるといまだにFirebaseはないと困りますね。

Discussion