Skip to main content

Overview

The Pengrafic template includes authentication pages and Supabase integration for user authentication. The implementation supports email/password authentication as well as OAuth providers like Google and GitHub.

Supabase Setup

Dependencies

The template includes the following Supabase packages in package.json:
package.json
{
  "dependencies": {
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/supabase-js": "^2.81.1"
  }
}

Installation

If you’re setting up a new project, install the dependencies:
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js

Authentication Flow

Sign In Page

The sign-in page is located at src/app/signin/page.tsx and includes:
  • Email/password form
  • Google OAuth button
  • GitHub OAuth button
  • Password recovery link
  • Sign up redirect
src/app/signin/page.tsx
import Link from "next/link";
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Sign In Page | Pengrafic - Ingresa",
  description: "Esta es la página de inicio de sesión para Pengrafic.",
};

const SigninPage = () => {
  return (
    <section className="relative z-10 overflow-hidden pb-16 pt-36 md:pb-20 lg:pb-28 lg:pt-[180px]">
      <div className="container">
        <div className="-mx-4 flex flex-wrap">
          <div className="w-full px-4">
            <div className="shadow-three mx-auto max-w-[500px] rounded bg-white px-6 py-10 dark:bg-dark sm:p-[60px]">
              <h3 className="mb-3 text-center text-2xl font-bold text-black dark:text-white sm:text-3xl">
                Sign in to your account
              </h3>
              <p className="mb-11 text-center text-base font-medium text-body-color">
                Login to your account for a faster checkout.
              </p>
              
              {/* Google Sign In Button */}
              <button className="border-stroke dark:text-body-color-dark dark:shadow-two mb-6 flex w-full items-center justify-center rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 hover:border-primary hover:bg-primary/5 hover:text-primary dark:border-transparent dark:bg-[#2C303B] dark:hover:border-primary dark:hover:bg-primary/5 dark:hover:text-primary dark:hover:shadow-none">
                <span className="mr-3">
                  {/* Google Icon SVG */}
                </span>
                Sign in with Google
              </button>

              {/* GitHub Sign In Button */}
              <button className="border-stroke dark:text-body-color-dark dark:shadow-two mb-6 flex w-full items-center justify-center rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 hover:border-primary hover:bg-primary/5 hover:text-primary dark:border-transparent dark:bg-[#2C303B] dark:hover:border-primary dark:hover:bg-primary/5 dark:hover:text-primary dark:hover:shadow-none">
                <span className="mr-3">
                  {/* GitHub Icon SVG */}
                </span>
                Sign in with Github
              </button>
              
              <div className="mb-8 flex items-center justify-center">
                <span className="hidden h-[1px] w-full max-w-[70px] bg-body-color/50 sm:block"></span>
                <p className="w-full px-5 text-center text-base font-medium text-body-color">
                  Or, sign in with your email
                </p>
                <span className="hidden h-[1px] w-full max-w-[70px] bg-body-color/50 sm:block"></span>
              </div>
              
              {/* Email/Password Form */}
              <form>
                <div className="mb-8">
                  <label htmlFor="email" className="mb-3 block text-sm text-dark dark:text-white">
                    Your Email
                  </label>
                  <input
                    type="email"
                    name="email"
                    placeholder="Enter your Email"
                    className="border-stroke dark:text-body-color-dark dark:shadow-two w-full rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 focus:border-primary dark:border-transparent dark:bg-[#2C303B] dark:focus:border-primary dark:focus:shadow-none"
                  />
                </div>
                <div className="mb-8">
                  <label htmlFor="password" className="mb-3 block text-sm text-dark dark:text-white">
                    Your Password
                  </label>
                  <input
                    type="password"
                    name="password"
                    placeholder="Enter your Password"
                    className="border-stroke dark:text-body-color-dark dark:shadow-two w-full rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 focus:border-primary dark:border-transparent dark:bg-[#2C303B] dark:focus:border-primary dark:focus:shadow-none"
                  />
                </div>
                
                <div className="mb-8 flex flex-col justify-between sm:flex-row sm:items-center">
                  <div className="mb-4 sm:mb-0">
                    <label htmlFor="checkboxLabel" className="flex cursor-pointer select-none items-center text-sm font-medium text-body-color">
                      <input type="checkbox" id="checkboxLabel" className="sr-only" />
                      Keep me signed in
                    </label>
                  </div>
                  <div>
                    <a href="#0" className="text-sm font-medium text-primary hover:underline">
                      Forgot Password?
                    </a>
                  </div>
                </div>
                
                <div className="mb-6">
                  <button className="shadow-submit dark:shadow-submit-dark flex w-full items-center justify-center rounded-sm bg-primary px-9 py-4 text-base font-medium text-white duration-300 hover:bg-primary/90">
                    Sign in
                  </button>
                </div>
              </form>
              
              <p className="text-center text-base font-medium text-body-color">
                Don't you have an account?{" "}
                <Link href="/signup" className="text-primary hover:underline">
                  Sign up
                </Link>
              </p>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
};

export default SigninPage;

Sign Up Page

The sign-up page at src/app/signup/page.tsx includes:
  • Full name field
  • Email field
  • Password field
  • Terms and conditions checkbox
  • OAuth provider buttons
src/app/signup/page.tsx
import Link from "next/link";
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Sign Up Page | Pengrafic - Regístrate",
  description: "Esta es la página de registro para Pengrafic.",
};

const SignupPage = () => {
  return (
    <section className="relative z-10 overflow-hidden pb-16 pt-36 md:pb-20 lg:pb-28 lg:pt-[180px]">
      <div className="container">
        <div className="shadow-three mx-auto max-w-[500px] rounded bg-white px-6 py-10 dark:bg-dark sm:p-[60px]">
          <h3 className="mb-3 text-center text-2xl font-bold text-black dark:text-white sm:text-3xl">
            Crea tu cuenta
          </h3>
          <p className="mb-11 text-center text-base font-medium text-body-color">
            Es totalmente gratis y muy fácil.
          </p>
          
          {/* OAuth Buttons */}
          {/* ... */}
          
          <form>
            <div className="mb-8">
              <label htmlFor="name" className="mb-3 block text-sm text-dark dark:text-white">
                Full Name
              </label>
              <input
                type="text"
                name="name"
                placeholder="Enter your full name"
                className="border-stroke dark:text-body-color-dark dark:shadow-two w-full rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 focus:border-primary dark:border-transparent dark:bg-[#2C303B] dark:focus:border-primary dark:focus:shadow-none"
              />
            </div>
            <div className="mb-8">
              <label htmlFor="email" className="mb-3 block text-sm text-dark dark:text-white">
                Work Email
              </label>
              <input
                type="email"
                name="email"
                placeholder="Enter your Email"
                className="border-stroke dark:text-body-color-dark dark:shadow-two w-full rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 focus:border-primary dark:border-transparent dark:bg-[#2C303B] dark:focus:border-primary dark:focus:shadow-none"
              />
            </div>
            <div className="mb-8">
              <label htmlFor="password" className="mb-3 block text-sm text-dark dark:text-white">
                Your Password
              </label>
              <input
                type="password"
                name="password"
                placeholder="Enter your Password"
                className="border-stroke dark:text-body-color-dark dark:shadow-two w-full rounded-sm border bg-[#f8f8f8] px-6 py-3 text-base text-body-color outline-none transition-all duration-300 focus:border-primary dark:border-transparent dark:bg-[#2C303B] dark:focus:border-primary dark:focus:shadow-none"
              />
            </div>
            
            <div className="mb-8 flex">
              <label htmlFor="checkboxLabel" className="flex cursor-pointer select-none text-sm font-medium text-body-color">
                <input type="checkbox" id="checkboxLabel" className="sr-only" />
                <span>
                  By creating account means you agree to the
                  <a href="#0" className="text-primary hover:underline"> Terms and Conditions</a>
                  , and our
                  <a href="#0" className="text-primary hover:underline"> Privacy Policy</a>
                </span>
              </label>
            </div>
            
            <div className="mb-6">
              <button className="shadow-submit dark:shadow-submit-dark flex w-full items-center justify-center rounded-sm bg-primary px-9 py-4 text-base font-medium text-white duration-300 hover:bg-primary/90">
                Sign up
              </button>
            </div>
          </form>
          
          <p className="text-center text-base font-medium text-body-color">
            Already using Startup?{" "}
            <Link href="/signin" className="text-primary hover:underline">
              Sign in
            </Link>
          </p>
        </div>
      </div>
    </section>
  );
};

export default SignupPage;

OAuth Callback Handler

The OAuth callback route handles the authentication flow after a user signs in with an OAuth provider.
src/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) {
    // Initialize Supabase client for server-side use
    const supabase = createRouteHandlerClient({ cookies });
    
    // Exchange the code for a session
    await supabase.auth.exchangeCodeForSession(code);
  }

  // Redirect to home page after authentication
  return NextResponse.redirect(requestUrl.origin);
}

Setting Up Supabase

1
Create a Supabase Project
2
  • Go to supabase.com
  • Create a new project
  • Note your project URL and anon key
  • 3
    Configure Environment Variables
    4
    Create a .env.local file in your project root:
    5
    NEXT_PUBLIC_SUPABASE_URL=your-project-url
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
    
    6
    Initialize Supabase Client
    7
    Create a Supabase client utility file:
    8
    import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
    
    export const supabase = createClientComponentClient();
    
    9
    Enable OAuth Providers
    10
    In your Supabase dashboard:
    11
  • Go to Authentication → Providers
  • Enable Google and GitHub providers
  • Add your OAuth credentials
  • Set the callback URL to: https://your-domain.com/auth/callback
  • Implementing Authentication Logic

    Sign In with Email

    "use client";
    import { useState } from 'react';
    import { supabase } from '@/lib/supabase';
    
    export default function SignInForm() {
      const [email, setEmail] = useState('');
      const [password, setPassword] = useState('');
      const [loading, setLoading] = useState(false);
    
      const handleSignIn = async (e: React.FormEvent) => {
        e.preventDefault();
        setLoading(true);
    
        const { error } = await supabase.auth.signInWithPassword({
          email,
          password,
        });
    
        if (error) {
          alert(error.message);
        } else {
          window.location.href = '/';
        }
        
        setLoading(false);
      };
    
      return (
        <form onSubmit={handleSignIn}>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
          />
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
          />
          <button type="submit" disabled={loading}>
            {loading ? 'Signing in...' : 'Sign In'}
          </button>
        </form>
      );
    }
    

    Sign In with OAuth

    "use client";
    import { supabase } from '@/lib/supabase';
    
    export default function OAuthButtons() {
      const handleGoogleSignIn = async () => {
        await supabase.auth.signInWithOAuth({
          provider: 'google',
          options: {
            redirectTo: `${window.location.origin}/auth/callback`,
          },
        });
      };
    
      const handleGitHubSignIn = async () => {
        await supabase.auth.signInWithOAuth({
          provider: 'github',
          options: {
            redirectTo: `${window.location.origin}/auth/callback`,
          },
        });
      };
    
      return (
        <>
          <button onClick={handleGoogleSignIn}>
            Sign in with Google
          </button>
          <button onClick={handleGitHubSignIn}>
            Sign in with GitHub
          </button>
        </>
      );
    }
    

    Sign Up with Email

    "use client";
    import { useState } from 'react';
    import { supabase } from '@/lib/supabase';
    
    export default function SignUpForm() {
      const [email, setEmail] = useState('');
      const [password, setPassword] = useState('');
      const [name, setName] = useState('');
    
      const handleSignUp = async (e: React.FormEvent) => {
        e.preventDefault();
    
        const { error } = await supabase.auth.signUp({
          email,
          password,
          options: {
            data: {
              full_name: name,
            },
          },
        });
    
        if (error) {
          alert(error.message);
        } else {
          alert('Check your email for the confirmation link!');
        }
      };
    
      return (
        <form onSubmit={handleSignUp}>
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="Full Name"
          />
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
          />
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
          />
          <button type="submit">Sign Up</button>
        </form>
      );
    }
    

    Check Authentication Status

    "use client";
    import { useEffect, useState } from 'react';
    import { supabase } from '@/lib/supabase';
    import type { User } from '@supabase/supabase-js';
    
    export default function UserProfile() {
      const [user, setUser] = useState<User | null>(null);
    
      useEffect(() => {
        // Get initial session
        supabase.auth.getSession().then(({ data: { session } }) => {
          setUser(session?.user ?? null);
        });
    
        // Listen for auth changes
        const { data: { subscription } } = supabase.auth.onAuthStateChange(
          (_event, session) => {
            setUser(session?.user ?? null);
          }
        );
    
        return () => subscription.unsubscribe();
      }, []);
    
      const handleSignOut = async () => {
        await supabase.auth.signOut();
      };
    
      if (!user) {
        return <p>Please sign in</p>;
      }
    
      return (
        <div>
          <p>Welcome, {user.email}</p>
          <button onClick={handleSignOut}>Sign Out</button>
        </div>
      );
    }
    
    Never commit your .env.local file to version control. Always use environment variables for sensitive data like API keys and secrets.

    Protected Routes

    Create a middleware to protect routes:
    middleware.ts
    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();
    
      // Redirect to signin if not authenticated
      if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {
        return NextResponse.redirect(new URL('/signin', req.url));
      }
    
      return res;
    }
    
    export const config = {
      matcher: ['/dashboard/:path*'],
    };
    

    Build docs developers (and LLMs) love