🔒

【Nextjs×Convex×Clerk】ソーシャルアカウントによるログイン認証の実装

2024/01/02に公開

はじめに

この記事は、GitHubやGmail、Microsoftアカウントなどのソーシャルアカウントによるログイン実装の手順を説明しています。
以下の動画で採用されていたGitHubアカウントによるログイン認証の実装を参考に、簡単なデモアプリを作成します。

Fullstack Notion Clone: Next.js 13, React, Convex, Tailwind | Full Course 2023

🔰私は絶賛フロントエンド学習中で、ログイン認証やプロバイダまわりでつまづくことが多々あるので、同じような方の参考になればと思います。
また、Convexはドキュメントがとても丁寧でチュートリアルも充実しているので覗いてみてください。

使用スタック

  • Next.js(14.0.4)
  • Tailwind CSS
  • Convex:バックエンド
  • Clerk:認証プラットフォーム

完成デモ

UIは最小限ですが、GitHub・Gmail・Microsoftアカウントからログインできます。Clerkの設定からログインに使用するソーシャルアカウントは変更可能です。
未ログインユーザのリダイレクトも防ぎます。
https://youtu.be/XEVK6TXtrNs

セットアップ

create-next-app

ターミナル
npx create-next-app@latest アプリ名
cd アプリ名

必要なパッケージをインストール

ターミナル
npm install convex
npm install @clerk/clerk-react

Convexプロジェクトの作成

ターミナル上で以下のコマンドを実行し、a new projectを選択、任意のプロジェクト名を入力します。

ターミナル
npx convex dev

Clerkアプリケーションの作成

Clerkにログインし、Add applicationを選択、サインインの方法を選択します。

作成後、API Keysを.env.localにコピーします。

.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***********
CLERK_SECRET_KEY=sk_test_**********

JWT Templateの作成
New JWT TemplateからConvexを選択します。

Apply changesボタンを押して、テンプレートを作成します。

configファイルの作成
convex/auth.config.jsを作成します。
先ほど作成したJWT Templateより、Issuerをコピーし、domainにペーストします。

convex/auth.config.js
export default {
    providers: [
      {
        domain: "https://****-****-****.clerk.accounts.dev",
        applicationID: "convex",
      },
    ],
  };

以上でセットアップは完了です!

フロントの作成

以下が本アプリのフォルダ構成になります。

 /
 ├── app
 |   ├── (loginpage)
 |   |   └── page.tsx
 |   ├── main
 |   |   ├── layout.tsx
 |   |   └── page.tsx
 |   ├── favicon.ico
 |   ├── globals.css
 |   └── layout.tsx   
 ├── components
 |   ├── provider
 |   |   └── convex-provider.tsx
 |   └── navigation.tsx
 :

ログイン(未認証時の)ページ、CSSは以下になります。ログイン認証とはあまり関係ありません。

loginpage
(loginpage)/page.tsx
"use client";

const LogInPage = () => {
  return (
    <div className="h-full">
      <div className="h-full flex justify-center items-center">
        Ligin page
      </div>
    </div>
  );
}

export default LogInPage;
globals.css
app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

html,
body,
:root {
  height: 100%;
}

Convexプロバイダーの作成

components/providerを作成、convex-provider.tsxを作成します。

convex-provider.tsx
"use client";

import { ReactNode } from "react";
import { ConvexReactClient } from "convex/react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ClerkProvider, useAuth } from "@clerk/clerk-react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export const ConvexClientProvider = ({ children }: { children: ReactNode }) => {
  return (
    <ClerkProvider
      publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}
    >
      <ConvexProviderWithClerk useAuth={useAuth} client={convex}>
        {children}
      </ConvexProviderWithClerk>
    </ClerkProvider>
  );
};

app直下のlayout.tsxにプロバイダーを追加し、監視できるようにします。

app/layout.tsx
export default function RootLayout({ children }: { children:React.ReactNode}) => {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ConvexClientProvider>
          {children}
        </ConvexClientProvider>
      </body>
    </html>
  )
}

ログインボタンの作成

components/navigation.tsxを作成します。

navigation.tsx
"use client";

import React from 'react';

import { useConvexAuth } from "convex/react";
import { SignInButton, UserButton } from "@clerk/clerk-react";

const Navigation = () => {
    const { isAuthenticated, isLoading } = useConvexAuth();

    return (
        <div className='w-full border-b'>
            <div className='m-2 flex justify-end'>
                {isLoading && (
                    <p>Loading...</p>
                )}
                {!isAuthenticated && !isLoading && (
                    <>
                        <SignInButton mode="modal" redirectUrl="/main">
                            <div className="px-5 py-2.5 relative rounded group overflow-hidden font-medium bg-zinc-50 text-zinc-600 inline-block cursor-pointer">
                                <span className="absolute top-0 left-0 flex w-full h-0 mb-0 transition-all duration-200 ease-out transform translate-y-0 bg-zinc-600 group-hover:h-full opacity-90"></span>
                                <span className="relative group-hover:text-white">Login</span>
                            </div>
                        </SignInButton>
                    </>
                )}
                {isAuthenticated && !isLoading && (
                    <UserButton
                        afterSignOutUrl="/"
                    />
                )}
            </div>
        </div>
    )
}

export default Navigation;

プロバイダの状態はuseConvexAuthから取得することができます。

useConvexAuth
const { isAuthenticated, isLoading } = useConvexAuth();

ログインボタンのスタイルは以下のサイトを参照しました。

TailwindCSS Buttons

ログインモーダルに関しては、@clerk/clerk-reactでコンポーネントが提供されているので、それをそのまま使用します。めちゃ簡単^ ^;

NavigationコンポーネントをLayoutに追加

app/layout.tsx
   <html lang="en">
      <body className={inter.className}>
        <ConvexClientProvider>
+         <Navigation/>
          {children}
        </ConvexClientProvider>
       </body>
    </html>

不正リダイレクト防止

app/main直下のlayout.tsxで、useConvexAuthを使用します。

main/layput.tsx
"use client";

import { useConvexAuth } from "convex/react";
import { ReactNode } from "react";
import { redirect } from "next/navigation";

const MainPageLayout = ({ children }:{ children:ReactNode }) => {
    const { isAuthenticated, isLoading } = useConvexAuth();
    
    if(isLoading){
        return (
            <div className="h-full flex justify-center items-center">
                <p>Loading...</p>
            </div>
        )
    }

    if (!isAuthenticated) {
        return redirect("/"); // app/(loginpage)/page.tsxに遷移
    }

    return (
        <div className="h-full">
            <main className="h-full">
                {children}
            </main>
        </div>
    )
}

export default MainPageLayout;

ユーザー情報の表示

main/page.tsx
"use client";

import { useUser } from "@clerk/clerk-react";
import React from "react";

const MainPage = () => {
    const { user } = useUser();

    return (
        <div className='h-full flex justify-center items-center'>
            <div className='flex flex-col items-center'>
                <p>Welcome! {user?.fullName}</p>
                <p>Main Page</p>
            </div>
        </div>
    )
}

export default MainPage

ログインユーザーの情報は@clerk/clerk-reactからuseUserをインポートすることで取得できます。今回はログインしたユーザーのフルネームを表示しています。

以上で終了です🚀

最後に

Convex・Clerkを使用し、簡単にログイン認証が実装できました。Convex自体もTypeScriptコードからDBスキーマを書けたり、リアルタイムなデーブルの更新を確認できたりと、バックエンドアプリケーションとしても使いやすいと感じたので、しっかりと理解した上で別記事にまとめてみたいなと思います。
また、人生で初めて技術系(?)記事を書きました。内容はもちろんですが、もっと綺麗な構成や書き方を身につけていきたいです、、

アプリのコードは以下のGitHubにあげています。
https://github.com/kazu0429/convex_auth_tutorial

参考文献

https://docs.convex.dev/quickstart/nextjs
https://docs.convex.dev/auth/clerk

Discussion