diff --git a/apps/web/content/guides/getstarted/meta.json b/apps/web/content/guides/getstarted/meta.json index 6a32fe851..263798065 100644 --- a/apps/web/content/guides/getstarted/meta.json +++ b/apps/web/content/guides/getstarted/meta.json @@ -5,6 +5,7 @@ "intro-to-ai", "solana-token-airdrop-and-faucets", "build-a-x402-facilitator", - "intro-to-x402" + "intro-to-x402", + "supabase-auth-guide" ] } diff --git a/apps/web/content/guides/getstarted/supabase-auth-guide.mdx b/apps/web/content/guides/getstarted/supabase-auth-guide.mdx new file mode 100644 index 000000000..dfa3455dc --- /dev/null +++ b/apps/web/content/guides/getstarted/supabase-auth-guide.mdx @@ -0,0 +1,679 @@ +--- +date: 2025-01-22T00:00:00Z +difficulty: beginner +title: "How to Authenticate Users with Solana Wallets Using Supabase" +seoTitle: "How to Authenticate Users with Solana Wallets Using Supabase" +description: + "Step-by-step guide to implement Solana wallet authentication in your Next.js + application using Supabase. Learn how to connect Phantom or Solflare wallets, + authenticate users with wallet signatures, and protect routes. Perfect for + developers building Solana dApps who want secure, passwordless authentication." +tags: + - auth + - supabase + - wallet + - nextjs +keywords: + - authentication + - supabase + - wallet authentication + - wallet authentication + - solana wallet + - next.js + - protected routes +--- + +# How to Authenticate Users with Solana Wallets Using Supabase + +## 1. Introduction + +This guide shows you how to build a Solana app with wallet authentication using +[Supabase](https://supabase.com). Users sign in with their Phantom or Solflare +wallet. No email. No passwords. Just a wallet signature. + +You'll be working with the +[Supabase authentication template](https://templates.solana.com/supabase-auth), +which provides a complete starting point for Solana applications with +wallet-based authentication. + +Traditional authentication means managing user accounts, password resets, and +email verification. With Solana wallet authentication, users prove they control +a wallet address. That's it. It's simpler for them. It's simpler for you. + +The template uses Supabase's wallet authentication. Supabase handles the +cryptographic verification. You get a managed +[PostgreSQL](https://www.postgresql.org/) database, session management, and +protected routes. No local database setup. Everything runs through Supabase's +hosted services. + +This guide is based directly on the code in the template. Every code snippet +comes from the actual implementation. Every file path is real. If you see +something here, you can find it in the template. + +What you'll learn: + +- How to set up Supabase wallet authentication for Solana wallets +- How wallet connection works separately from Supabase authentication +- How to protect routes so only authenticated users can access them +- How the authentication flow works under the hood + +By the end, you'll understand why each piece exists. You'll know how to extend +it for your own needs. If you see an error message you don't recognize, don't +stress. We'll cover the common ones and how to fix them. + +## 2. What You'll Build + +After following this guide, you'll have a working [Next.js](https://nextjs.org) +app with wallet authentication built from the +[Supabase authentication template](https://templates.solana.com/supabase-auth). +The app includes a home page with wallet connection, a protected account page, +and a complete authentication system using Supabase. + +## 3. Why This Matters + +Building your own Solana authentication system requires handling message +signing, signature verification, session management, and database storage. This +template uses Supabase's wallet authentication, so you just call +`supabase.auth.signInWithWeb3()` and it works. With Solana wallet +authentication, users sign in with their wallet, no passwords, no email +verification, just one click. + +## 4. Prerequisites + +Before you start, make sure you have these installed. If you're missing +something, the app won't run. Don't worry, we'll check each one. + +**Node.js v24.11.1(LTS)** + +The template uses Next.js. It requires Node.js v24.11.1(LTS). Check your +version: + +```bash +node --version +``` + +If you see Node.js v24.11.1(LTS), you're good. If not, install +[Node.js](https://nodejs.org) from [nodejs.org](https://nodejs.org). Use the LTS +version. It's the most stable. + +Next.js runs on Node.js. It handles server-side rendering, API routes, and the +build process. Without it, `npm install` won't work. + +**A Supabase account** + +You need a Supabase account to use wallet authentication. The free tier works +fine for development. Sign up at [supabase.com](https://supabase.com). It takes +about two minutes. + +The template uses Supabase's wallet authentication feature. You'll create a +project and get credentials. Those credentials go in your `.env.local` file. +Without them, authentication won't work. + +You'll need two things from your Supabase project: + +- Project URL (looks like `https://xxxxx.supabase.co`) +- Anon key (the public key, not the service role key) + +You can find both in your Supabase dashboard under Settings → API. Don't stress +if you don't have them yet. We'll get them during setup. + +**A Solana wallet extension** + +You need a Solana wallet installed in your browser. Phantom, Solflare, or +Backpack all work. Install one from their official websites. Make sure it's the +browser extension, not a mobile app. + +Users sign in with their wallet. The template detects `window.solana` to +connect. Without a wallet extension, the "Connect Wallet" button won't work. You +can't test authentication without it. + +**Optional: A code editor** + +Use VS Code, Cursor, or any editor you prefer. The template uses TypeScript, so +an editor with [TypeScript](https://www.typescriptlang.org/) support helps. But +it's not required. You can use any text editor. + +That's it. If you have Node.js v24.11.1(LTS), a Supabase account, and a Solana +wallet, you're ready. If you're missing something, install it now. We'll wait. + +## 5. Setting Up the Project + +Follow these steps in order. If you skip a step, something will break. Don't +worry, we'll check each step as we go. + + + + +1. Create the project + +Create a new project using the Supabase authentication template: + +```bash +npx -y create-solana-dapp@latest -t solana-foundation/templates/community/supabase-auth +``` + +![Click "Use This Template"](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763757062/Screenshot_2025-11-21_at_5.17.20_PM_uygpei.png) + +![Copy The Command](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763757064/Screenshot_2025-11-21_at_5.16.10_PM_zintc3.png) + +This creates a new project directory with all the template code. You can find +the template at +[templates.solana.com/supabase-auth](https://templates.solana.com/supabase-auth). + +Navigate into the project directory: + +```bash +cd supabase-auth +``` + +You need to be in this directory for all the commands below. + + + + +2. Install dependencies + +```bash +npm install +``` + + + + +3. Create your Supabase project + +You need a Supabase project before you can authenticate. Here's how to create +one: + +1. Go to [supabase.com](https://supabase.com) + ![Go To Supabase](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763816332/Screenshot_2025-11-22_at_9.46.54_AM_gt6tz1.png) + +2. Sign in with using your preferred Sign In Method. + ![Sign In With Github or Email](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763816333/Screenshot_2025-11-22_at_9.47.10_AM_g1yror.png) + +3. Click "New Project". + ![Click On New Project](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763816333/Screenshot_2025-11-22_at_9.48.10_AM_cphnl1.png) + +4. Fill in your project details: + - Name it something memorable (like "solana-auth-test") + - Set a database password (save this somewhere safe) + - Choose a region close to you + ![Create Your Project](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763816333/Screenshot_2025-11-22_at_9.48.35_AM_quesek.png) + +5. Wait for the project to finish provisioning (about 2 minutes) + +Once your project is ready, you'll see the project dashboard. Don't close this +tab. You'll need it for the next steps. + +Supabase needs a project to store user sessions and handle authentication. Each +project has its own database and API keys. The free tier gives you everything +you need for development. + + + + +4. Enable Web3 Authentication in Supabase + + + +This step is critical. Without it, authentication will fail with a 422 error. +Don't skip this. + + + +1. In your Supabase dashboard, go to **Authentication** → **Providers** +2. Scroll down to find **Web3** (it may be under "Additional Providers" or + "Additional") +3. Toggle **Web3** to enabled +4. Save the changes + +![Supabase Web3 authentication toggle enabled](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763204891/Screenshot_2025-11-15_at_8.06.58_AM_tnivte.png) + +If you don't see Web3 as an option, your Supabase project might not support it +yet. Wallet authentication is still in beta for some projects. Try creating a +new project or check the Supabase documentation for the latest status. + +The template uses `supabase.auth.signInWithWeb3()`. This method only works if +wallet authentication is enabled in your project. If it's disabled, you'll get a +422 error when trying to sign in. The error message will say "Web3 provider is +disabled". If you see that, come back to this step. + + + + +5. Get your Supabase credentials + +You need two values from your Supabase project: + +1. In your Supabase dashboard, go to **Settings** → **API** +2. Copy your **Project URL** (looks like `https://xxxxx.supabase.co`) +3. Copy your **anon/public key** (the `anon` key, not the `service_role` key) + +Keep these handy. You'll paste them into a file in the next step. + +The Project URL tells the Supabase client where to connect. The anon key is the +public key that allows client-side access. The service role key is more powerful +and should never be used in client-side code. That's why we use the anon key. + + + + +6. Configure environment variables + +Create a `.env.local` file from the example: + +```bash +cp .env.example .env.local +``` + +Open `.env.local` in your editor. You'll see: + +```env +NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co" +NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key-here" +``` + +Replace the placeholder values with your actual Supabase credentials: + +```env +NEXT_PUBLIC_SUPABASE_URL="https://xxxxx.supabase.co" +NEXT_PUBLIC_SUPABASE_ANON_KEY="your-actual-anon-key-here" +``` + +Make sure the URL is in quotes. Make sure the key is in quotes. Don't add any +extra spaces. + +Next.js automatically loads `.env.local`. It ignores it in git, so your secrets +stay local. Never commit `.env.local` to version control. The `.env.example` +file is safe to commit because it has placeholder values. + + + + +7. Run the project + +Start the development server: + +```bash +npm run dev +``` + +This starts Next.js with Turbopack (faster than the default webpack). You should +see output like: + +```text +▲ Next.js 16.0.3 +- Local: http://localhost:3000 +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser. You should +see a "gm" screen with a dashboard. That's how you know it's working. + +If the page loads but shows errors in the browser console, don't stress. We'll +cover common errors in the Troubleshooting section. For now, if you see the "gm" +screen, you're good to go. + + + + +## 6. Connecting Your Wallet + + + +Make sure you have a Solana wallet extension installed (Phantom, Solflare, or +Backpack) before continuing. Without a wallet, you won't be able to connect or +authenticate. + + + +Wallet connection happens before authentication. You connect your wallet to read +blockchain data. Then you authenticate with Supabase to create a session. +They're separate steps. + +### Finding the wallet dropdown + +Look at the header of the page. You'll see a button that says "Select Wallet" or +"Connect Wallet". That's the wallet dropdown. It's in +`src/components/wallet-dropdown.tsx`. + +![Wallet dropdown in the header](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763205080/Screenshot_2025-11-15_at_8.11.08_AM_dyci3x.png) + +If you don't see it, check that your wallet extension is installed. The wallet +dropdown detects installed wallets automatically. If no wallets are detected, +the dropdown shows a link to get a Solana wallet. + +### Connecting your wallet + +Click the "Select Wallet" button. A dropdown menu appears. It lists all detected +wallets. You'll see options like: + +- Phantom +- Solflare +- Backpack +- Other installed wallets + +Click on your wallet. Your wallet extension will open a popup. It asks you to +approve the connection. Click "Approve" or "Connect" in the popup. + +Once connected, the button in the header changes. It shows your wallet address +truncated (like `J4AJ...MAAP`). That's how you know it's connected. + +![Connected wallet showing truncated address](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763205189/Screenshot_2025-11-15_at_8.12.57_AM_afhpjb.png) + +**What happens behind the scenes:** The wallet dropdown handles the connection +internally. When you click a wallet in the dropdown, it manages calling +`window.solana.connect()` behind the scenes. You don't need to call it directly. +The connection state is managed automatically. The `useSolana` hook exposes it +to your components. + +## 7. Signing In with Solana + +Once your wallet is connected, you can authenticate with Supabase. This creates +a session that persists across page refreshes. You'll be able to access +protected routes. + +Authentication creates a Supabase session that lets you access protected routes +and store user data. Without authentication, you can't access `/account` or +other protected pages. + +### Finding the sign-in card + +Scroll down on the home page. You'll see a card titled "Sign in with Solana". +It's in the dashboard section. The component is +`src/components/auth/wallet-login.tsx`. + +If you haven't connected your wallet yet, the card shows "Connect Wallet First" +and the button is disabled. Connect your wallet first, then come back to this +card. + +![Sign in with Solana card](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763205326/Screenshot_2025-11-15_at_8.15.12_AM_crhf6r.png) + +### The authentication flow + +Once your wallet is connected, the card shows "Wallet Connected: [your address]" +in green text. Click the **Sign in with Solana** button. + +Your wallet extension will open a popup. It asks you to sign a message. The +message says "Please sign this message to authenticate with your wallet." This +is the authentication challenge. + +Click "Approve" or "Sign" in your wallet popup. The wallet signs the message +cryptographically. This proves you control the wallet address. + +**How it works:** Your wallet signs a message that Supabase verifies. If the +signature matches your wallet address, Supabase creates a session and you're +authenticated. + +![Authentication flow diagram](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763808852/Screenshot_2025-11-22_at_7.50.13_AM_tsggmv.png) + +![Wallet popup asking to sign authentication message](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763205749/Screenshot_2025-11-15_at_8.18.19_AM_dqjptf.png) + +If authentication succeeds, you'll see "Wallet authenticated successfully!" in +green text. The card updates automatically to show a welcome message with your +wallet address and buttons to view account details or sign out. The UI updates +via the `AuthProvider` state changes - no manual navigation is needed. + +### Verifying authentication + +After signing in, check the card. It should show "Welcome Back!" with your +wallet address. You'll see two buttons: "View Account Details" and "Sign Out". + +The `AuthProvider` component automatically updates the UI when authentication +state changes. Any component using `useAuth` gets the updated state. + +### Session persistence + +The session persists across page refreshes. Users don't have to sign in every +time they visit. The session lasts until they sign out or it expires. + +### Accessing protected routes + +Once authenticated, you can access protected routes. Try clicking "View Account +Details" in the welcome card. It takes you to `/account`. Or navigate to +`/account` directly in your browser. + +If you're not authenticated and try to access `/account`, the `ProtectedRoute` +component redirects you to the home page. It checks `useAuth()` for the user. If +`user` is null, it redirects. + +The protected route logic is in `src/components/auth/protected-route.tsx`. It +wraps the `/account` page content. You can use it to protect any route you want. + +### Signing out + +To sign out, click the "Sign Out" button in the welcome card. This signs you out +from Supabase and disconnects your wallet, giving you a clean slate. + +### Common authentication errors + +**"Authentication failed: Web3 provider is disabled"** + +This means wallet authentication isn't enabled in your Supabase project. Don't +stress. Go back to your Supabase dashboard. Navigate to **Authentication** → +**Providers**. Find **Web3** and toggle it to enabled. Save the changes. Then +try signing in again. + +**"Authentication failed: [422 error]"** + +A 422 error usually means one of these: + +- Wallet authentication isn't enabled (see above) +- Your Supabase project doesn't support wallet authentication (check if you're + on a supported plan) +- The `window.solana` provider isn't properly connected + +If you see a 422 error, check the browser console. You might see more details +about what went wrong. The error message from Supabase usually tells you what's +missing. + +**"Solana wallet not detected"** + +This means `window.solana` is not available. Make sure you have a Solana wallet +extension installed and enabled. Make sure you've connected your wallet before +trying to authenticate. The wallet connection must happen first. + +**"Please connect a wallet first"** + +This message appears if you try to sign in without connecting your wallet. +Connect your wallet using the dropdown in the header first. Then come back to +the sign-in card. + +**"Failed to authenticate with wallet. Please try again."** + +This is a generic error. Check the browser console for more details. It might be +a network issue or a problem with the Supabase connection. Make sure your +`.env.local` file has the correct Supabase credentials. + +### Understanding the authentication state + +The `useAuth` hook gives you access to authentication state (`user`, `session`, +`loading`, `signOut`). You can use this hook in any component to check +authentication status. + +## 8. Exploring Protected Routes + +Protected routes require authentication. Unauthenticated users can't access +them. The template includes a `ProtectedRoute` component that handles this. (See +[Architecture Overview](#4-architecture-overview) for how it works.) + +### Understanding the ProtectedRoute component + +The `ProtectedRoute` component is in `src/components/auth/protected-route.tsx`. +It wraps any content you want to protect. It checks authentication status and +handles redirects. + +Here's how it works: + +1. It uses the `useAuth` hook to get the current user +2. If `loading` is true, it shows a loading spinner +3. If `user` is null (not authenticated), it redirects to home +4. If authenticated, it renders the children + +The component uses `router.replace('/')` to redirect. This replaces the current +URL in history. Users can't go back to the protected page after being +redirected. + +### The /account route + +The `/account` route is protected. The page file is `src/app/account/page.tsx`. +It wraps the account content with `ProtectedRoute`: + +```tsx + + + +``` + +If you're not authenticated and try to visit `/account`, you'll be redirected to +the home page. The redirect happens automatically. You'll see a brief loading +state, then the redirect. + +Redirecting is better UX than showing an error. Users know they need to sign in. +They're taken to a place where they can sign in. The redirect is instant, so it +doesn't feel broken. + +### The account page flow + +The account page has two parts: + +1. **Index page** (`/account`) - Redirects to the detail page if a wallet is + connected +2. **Detail page** (`/account/[address]`) - Shows account details for a specific + address + +Both routes are protected. The `/account/[address]` route +(`src/app/account/[address]/page.tsx`) is also wrapped with `ProtectedRoute`. +You can navigate directly to `/account/[address]` if you know the address, but +you must be authenticated first. + +The index page (`src/features/account/account-feature-index.tsx`) checks if a +wallet is connected. If `account` exists, it redirects to `/account/[address]`. +If not, it shows a wallet dropdown so you can connect. + +This flow ensures you always see account details for a connected wallet. If +you're authenticated but don't have a wallet connected, you can still connect +one. + +### What you'll see on the account page + +Once authenticated and on `/account/[address]`, you'll see: + +- **Account balance** - Shows SOL balance for the address +- **Account buttons** - Actions like send and receive +- **Token list** - Shows SPL tokens held by the address +- **Transaction history** - Shows recent transactions for the address + +![Account page showing balance, tokens, and transactions](https://res.cloudinary.com/resourcefulmind-inc/image/upload/v1763205913/Screenshot_2025-11-15_at_8.24.57_AM_cfgmwf.png) + +The page uses TanStack Query to fetch data. It caches responses for better +performance. If you refresh, it might show cached data while fetching fresh +data. + +The account detail component is in +`src/features/account/account-feature-detail.tsx`. It extracts the address from +the URL params. It validates the address using Gill's `assertIsAddress`. If the +address is invalid, it shows an error. + +### Testing protected routes + +To test the protection, try this: + +1. Sign out (if you're signed in) +2. Try to navigate to `/account` directly in your browser +3. You should be redirected to the home page + +You can also check the browser console. You'll see a log message: "Protected +route: User not authenticated, redirecting to home". This confirms the +protection is working. + +If you're authenticated and visit `/account`, you'll see the account page. The +`ProtectedRoute` component checks authentication and renders the content. + +### Creating your own protected routes + +You can protect any route by wrapping it with `ProtectedRoute`. For example, to +protect a `/dashboard` route: + +```tsx +// src/app/dashboard/page.tsx +import { ProtectedRoute } from "@/components/auth/protected-route"; + +export default function DashboardPage() { + return ( + +
Your protected content here
+
+ ); +} +``` + +The component handles all the authentication checks. You don't need to write +redirect logic yourself. + +You can also provide a custom fallback: + +```tsx +Custom "please sign in" message}> + + +``` + +The fallback renders instead of the default "Authentication Required" card. Use +this if you want custom messaging or styling. + +### Understanding the loading state + +The `ProtectedRoute` component shows a loading state while checking +authentication. This happens on the first render. The `AuthProvider` calls +`supabase.auth.getSession()` on mount. Until that completes, `loading` is true. + +Without a loading state, unauthenticated users might briefly see protected +content. The loading state prevents that flash. It's a better UX. + +The loading state is a spinner in a card. It says "Loading..." and "Checking +authentication status". It's centered on the screen. Once authentication is +checked, it either redirects or shows the content. + +### Common issues with protected routes + +**Redirect loop** + +If you see a redirect loop, check that your home page doesn't redirect to a +protected route. The home page should be accessible without authentication. If +it's protected, you'll get stuck in a loop. + +**Loading state never ends** + +If the loading spinner never goes away, check the browser console. You might see +an error from Supabase. Make sure your `.env.local` file has the correct +Supabase credentials. The `AuthProvider` can't check sessions without valid +credentials. + +**Can't access protected route even when authenticated** + +If you're signed in but still get redirected, check the browser console. You +might see an error about the session. Try signing out and signing back in. The +session might be invalid or expired. + +**Protected route shows content but user is null** + +This shouldn't happen, but if it does, there's a race condition. The +`ProtectedRoute` checks `user` from `useAuth`. If the auth state updates after +the check, you might see content briefly. This is rare, but if you see it, +refresh the page. + +### Why this pattern works + +The `ProtectedRoute` pattern is simple and reusable. Wrap any content you want +to protect. The component handles all the logic. It works with Next.js App +Router as a client component that can use hooks. For larger apps, you might want +middleware, but for most cases, this component is enough. + +## 9. Conclusion + +You've successfully set up a Solana application with wallet authentication using +Supabase. You can now connect wallets, authenticate users, and protect routes. + +Start building your features and customize the template to fit your needs. The +template provides a solid foundation for Solana applications with wallet-based +authentication.