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 inpackage.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 atsrc/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 atsrc/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
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
export const supabase = createClientComponentClient();
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*'],
};