👤 Privyによるユーザー認証の実装
このレ ッスンでは、Privy を導入して、ユーザーがEメールやソーシャルアカウントで簡単にログインできるようにし、各ユーザーに専用のウォレット(Embedded Wallet)を自動的に提供する機能を実装します!
これにより、web3に馴染みのないユーザーでも、まるで普段使っているWebサービスのように、シームレスに私たちのdAppを利用開始できるようになります!
🔑 Privyとは?
Privyは、web3アプリケーションの オンボーディング(新規ユーザー登録・利用開始プロセス) を劇的に簡単にするための強力なツールキットです。主な機能は以下の通りです。
-
柔軟な認証方法:
Eメール、SMS、Google、X(旧Twitter)などのソーシャルログインを提供し、ユーザーが好きな方法で認証できるようにします。 -
Embedded Wallets:
ユーザーがログインすると、バックグラウンドで自動的にスマートコントラクトウォレットが作成・管理されます。ユーザーは秘密鍵やシードフレーズを意識する必要が一切ありません。
これがweb3のマスアダプション(大衆化)への鍵となります。
-
外部ウォレット連携:
もちろん、MetaMaskやPhantomなど、ユーザーがすでに持っている既存のウォレットを接続することもサポートしています。
このプロジェクトでは、Privyを使ってユーザー認証とウォレット管理を驚くほどシンプルに実装します。
🛠 Privyのセットアップ
まず、フロントエンドプロジェクトでPrivyを利用するためのパッケージをインストールします。
cd pkgs/frontend
pnpm install @privy-io/react-auth @privy-io/export-wallets
次に、Privyの管理画面であなたのアプリケーションを登録し、App ID を取得する必要があります。
- Privyの公式サイトにアクセスし、サインアップまたはログインします。
- ダッシュボードで新しいアプリケーションを作成します。
- 作成したアプリケーションのページで、
APP ID
と書かれた文字列をコピーします。
取得したApp ID
を、pkgs/frontend/.env.local
ファイルに保存します。
NEXT_PUBLIC_PRIVY_APP_ID="YOUR_PRIVY_APP_ID"
※ YOUR_PRIVY_APP_ID
の部分は、実際に取得したご自身のIDに置き換えてください。
🔌 PrivyProviderの組み込み
Privyの機能をアプリケーション全体で利用できるようにするため、ルートコンポーネントをPrivyProvider
でラップします。
この設定はクライアントサイドでのみ有効にする必要があるため、専用のプロバイダーコンポーネントを作成するのがベストプラクティスです。
providers/privy-providers.tsx
というファイルを作成し、以下のコードを記述します。
// pkgs/frontend/providers/privy-providers.tsx
"use client";
import { PrivyProvider } from "@privy-io/react-auth";
import type React from "react";
interface PrivyProvidersProps {
children: React.ReactNode;
}
/**
* Privyプロバイダーのラッパーコンポーネント
*/
export const PrivyProviders: React.FC<PrivyProvidersProps> = ({ children }) => {
// 環境変数からPrivy設定を取得
const privyAppId = process.env.NEXT_PUBLIC_PRIVY_APP_ID || "test-app-id";
return (
<PrivyProvider
appId={privyAppId}
config={{
// ログイン方法の設定
loginMethods: ["email", "wallet", "google"],
// 外観の設定
appearance: {
theme: "dark",
accentColor: "#3B82F6",
},
// エンベデッドウォレット の設定
embeddedWallets: {
createOnLogin: "users-without-wallets",
},
}}
>
{children}
</PrivyProvider>
);
};
React Hot Toastの組み込み
ここでもう1つアプリ全体でToasterを使えるようにするためのProviderコンポーネントを追加します。
providers/toaster-provider.tsx
というファイルを作成し、以下のコードを記述します。
// pkgs/frontend/providers/toaster-provider.tsx
import type React from "react";
import { Toaster } from "react-hot-toast";
interface ToasterProviderProps {
children: React.ReactNode;
}
/**
* React Hot Toastの設定コンポーネント
* @param children 子要素
*/
export const ToasterProvider: React.FC<ToasterProviderProps> = ({
children,
}) => {
return (
<>
{children}
<Toaster
position="top-right"
reverseOrder={false}
gutter={8}
containerClassName=""
containerStyle={{}}
toastOptions={{
// デフォルトの設定
className: "",
duration: 4000,
style: {
background: "#363636",
color: "#fff",
borderRadius: "8px",
border: "1px solid rgba(255, 255, 255, 0.1)",
boxShadow:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
},
// 成功時の設定
success: {
duration: 3000,
style: {
background: "linear-gradient(135deg, #10B981 0%, #059669 100%)",
color: "#fff",
},
iconTheme: {
primary: "#fff",
secondary: "#10B981",
},
},
// エラー時の設定
error: {
duration: 5000,
style: {
background: "linear-gradient(135deg, #EF4444 0%, #DC2626 100%)",
color: "#fff",
},
iconTheme: {
primary: "#fff",
secondary: "#EF4444",
},
},
// 読み込み中の設定
loading: {
duration: Number.POSITIVE_INFINITY,
style: {
background: "linear-gradient(135deg, #3B82F6 0%, #2563EB 100%)",
color: "#fff",
},
iconTheme: {
primary: "#fff",
secondary: "#3B82F6",
},
},
}}
/>
</>
);
};
🔍 コード解説
-
PrivyProvider
:
このコンポーネントでアプリケーション全体をラップすることで、どのコンポーネントからでもusePrivy
という便利なフックを使って、ユーザーの認証情報やウォレットの状態にアクセスできるようになります。 -
appId
:
環境変数ファイル(.env.local
)から先ほど設定したApp IDを安全に読み込みます。!
は、この値が必ず存在することをTypeScriptに伝えています。 -
config
:
ログインモーダルの詳細設定です。-
loginMethods
:
ユーザーに提示するログイン 方法を配列で指定します。
ここではEメールと外部ウォレットでのログインを許可しています。 -
embeddedWallets
:
ウォレットを持っていないユーザー(例:Eメールでログインしたユーザー)には、ログイン時に自動でEmbedded Walletを作成するように設定しています。
-
🌐 アプリケーションへの適用
作成したProviders
コンポーネントを、アプリケーションのルートレイアウトに適用します。
これにより、すべてのページでPrivyの機能が有効になります。
app/layout.tsx
を以下のように修正してください。
// pkgs/frontend/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { PrivyProviders } from "@/providers/privy-providers"; // 👈 インポート
import { ToasterProvider } from "@/providers/toaster-provider"; // 👈 インポート
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Serverless ZK NFT App",
description: "Mint a ZK NFT with a secret password.",
};
/**
* RootLayout コンポーネント
* @param param0
* @returns
*/
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja">
<body className={inter.className}>
<PrivyProviders>
<ToasterProvider>{children}</ToasterProvider>
</PrivyProviders>
</body>
</html>
);
}