Overview
The platform uses Google OAuth via Supabase Auth for admin dashboard access. Authentication is domain-restricted to @nj.sgadi.us email addresses for security.
Features
Google OAuth Sign-In - One-click authentication via Google accounts
Domain Restriction - Only @nj.sgadi.us emails can access admin features
Session Management - Cookie-based sessions with automatic refresh
Redirect Preservation - Returns users to intended page after auth
Row Level Security - Database-level access control tied to user email
Setup
Prerequisites
Environment Variables
Add to .env.local: NEXT_PUBLIC_SUPABASE_URL = https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY = eyJhbGc...
Google OAuth Configuration
Create OAuth Credentials
Go to Google Cloud Console → APIs & Services → Credentials
Click Create Credentials → OAuth client ID
Choose Web application
Configure Authorized Origins
Add these JavaScript origins: http://localhost:3000
https://njrajatmahotsav.com
Configure Redirect URIs
Add these redirect URIs: https://your-project.supabase.co/auth/v1/callback
http://localhost:3000/auth/callback
https://njrajatmahotsav.com/auth/callback
Copy Client ID and Secret
Save the Client ID and Client Secret for the next step.
Supabase Configuration
Enable Google Provider
Go to Supabase Dashboard → Authentication → Providers
Find Google and toggle it on
Paste your Client ID and Client Secret
Save changes
Configure Redirect URLs
Go to Authentication → URL Configuration
Add these to Redirect URLs :
http://localhost:3000/auth/callback
https://njrajatmahotsav.com/auth/callback
Set Site URL to https://njrajatmahotsav.com
Authentication Flow
Sign-In Component
The admin sign-in button triggers the Google OAuth flow:
app/admin/registrations/AdminSignIn.tsx
Usage Example
"use client"
import { useState } from "react"
import { supabase } from "@/utils/supabase/client"
import { Button } from "@/components/atoms/button"
import { Loader2 } from "lucide-react"
export function AdminSignIn () {
const [ loading , setLoading ] = useState ( false )
const handleGoogleSignIn = async () => {
setLoading ( true )
try {
const nextPath = "/admin/registrations"
const cookieParts = [
`rm-auth-next= ${ encodeURIComponent ( nextPath ) } ` ,
"Path=/" ,
"Max-Age=600" ,
"SameSite=Lax" ,
]
// Ensure the cookie survives redirects between www and apex domain
const host = window . location . hostname
if ( host . endsWith ( "njrajatmahotsav.com" )) {
cookieParts . push ( "Domain=.njrajatmahotsav.com" )
}
if ( window . location . protocol === "https:" ) cookieParts . push ( "Secure" )
document . cookie = cookieParts . join ( "; " )
const { error } = await supabase . auth . signInWithOAuth ({
provider: "google" ,
options: {
redirectTo: ` ${ window . location . origin } /auth/callback` ,
},
})
if ( error ) throw error
} catch ( err ) {
console . error ( "Sign-in error:" , err )
setLoading ( false )
}
}
return (
< div className = "flex flex-col items-center gap-6" >
< Button
onClick = { handleGoogleSignIn }
disabled = { loading }
className = "inline-flex items-center gap-2 rounded-full px-8 py-4"
>
{ loading ? (
< Loader2 className = "size-5 animate-spin" />
) : (
< svg className = "size-5" viewBox = "0 0 24 24" >
{ /* Google icon SVG paths */ }
</ svg >
) }
{ loading ? "Signing in…" : "Sign in with Google" }
</ Button >
< p className = "text-sm text-gray-600 max-w-xs text-center" >
Access restricted to @nj.sgadi.us accounts
</ p >
</ div >
)
}
The component stores the intended redirect path (/admin/registrations) in a cookie to preserve it through the OAuth flow, which may drop query parameters.
Callback Handler
The callback route exchanges the OAuth code for a session:
app/auth/callback/route.ts
import { NextResponse } from "next/server"
import { cookies } from "next/headers"
import { createClient } from "@/utils/supabase/server"
/**
* OAuth callback route. Exchanges auth code for session and redirects.
*/
export async function GET ( request : Request ) {
const { searchParams , origin } = new URL ( request . url )
const code = searchParams . get ( "code" )
const queryNext = searchParams . get ( "next" )
let next = queryNext ?? "/"
// Check cookie for redirect path
if ( ! queryNext ) {
const cookieStore = await cookies ()
const cookieNext = cookieStore . get ( "rm-auth-next" )?. value
if ( cookieNext ) next = decodeURIComponent ( cookieNext )
}
// Validate redirect path (prevent open redirects)
if ( ! next . startsWith ( "/" ) || next . includes ( "//" ) || next . includes ( ":" )) {
next = "/"
}
if ( ! code ) {
return NextResponse . redirect ( ` ${ origin } /auth/auth-code-error` )
}
const supabase = await createClient ()
const { error } = await supabase . auth . exchangeCodeForSession ( code )
if ( error ) {
console . error ( "[auth/callback] exchangeCodeForSession error:" , error . message )
return NextResponse . redirect ( ` ${ origin } /auth/auth-code-error` )
}
const forwardedHost = request . headers . get ( "x-forwarded-host" )
const requestHost = request . headers . get ( "host" )
const effectiveHost = forwardedHost ?? requestHost ?? new URL ( request . url ). host
const isLocalEnv = process . env . NODE_ENV === "development"
const redirectUrl = isLocalEnv
? ` ${ origin }${ next } `
: forwardedHost
? `https:// ${ forwardedHost }${ next } `
: ` ${ origin }${ next } `
const response = NextResponse . redirect ( redirectUrl )
// Clear the redirect cookie
const clearCookieOptions : Parameters < typeof response . cookies . set >[ 2 ] = {
path: "/" ,
maxAge: 0 ,
}
if ( effectiveHost . endsWith ( "njrajatmahotsav.com" )) {
clearCookieOptions . domain = ".njrajatmahotsav.com"
}
response . cookies . set ( "rm-auth-next" , "" , clearCookieOptions )
return response
}
The callback route validates redirect paths to prevent open redirect attacks . Only relative paths starting with / are allowed.
Domain Restriction
Admin Auth Helper
Domain checking is centralized in lib/admin-auth.ts:
lib/admin-auth.ts
Usage in API Route
const ALLOWED_DOMAIN = "@nj.sgadi.us"
/**
* Checks if the given email is allowed for admin access.
* @param email - User email (e.g. from session.user.email)
* @returns true if email ends with @nj.sgadi.us (case-insensitive)
*/
export function isAllowedAdminDomain ( email : string | null | undefined ) : boolean {
if ( ! email || typeof email !== "string" ) return false
return email . toLowerCase (). endsWith ( ALLOWED_DOMAIN . toLowerCase ())
}
/**
* Checks if the given user has an allowed admin domain email.
* @param user - User object with optional email (e.g. from session.user)
* @returns true if user exists and email ends with @nj.sgadi.us
*/
export function isAdminDomainUser ( user : { email ?: string | null } | null | undefined ) : boolean {
return isAllowedAdminDomain ( user ?. email )
}
Server Component Protection
import { redirect } from 'next/navigation'
import { createClient } from '@/utils/supabase/server'
import { isAdminDomainUser } from '@/lib/admin-auth'
export default async function AdminPage () {
const supabase = await createClient ()
const { data : { user } } = await supabase . auth . getUser ()
if ( ! user ) {
redirect ( '/admin/login' )
}
if ( ! isAdminDomainUser ( user )) {
redirect ( '/admin/unauthorized' )
}
return (
< div >
< h1 > Welcome , { user . email } </ h1 >
{ /* Admin dashboard content */ }
</ div >
)
}
Session Management
Middleware Auto-Refresh
The middleware automatically refreshes sessions on every request:
import { updateSession } from "@/utils/supabase/middleware"
export async function middleware ( request : NextRequest ) {
return await updateSession ( request )
}
See the Supabase Integration page for the full implementation in utils/supabase/middleware.ts:10.
Manual Sign Out
import { supabase } from '@/utils/supabase/client'
async function handleSignOut () {
const { error } = await supabase . auth . signOut ()
if ( error ) {
console . error ( 'Sign out error:' , error )
} else {
window . location . href = '/'
}
}
Security Best Practices
Always Verify Domain Server-Side
Never trust client-side checks alone. Always verify isAdminDomainUser() in API routes and Server Components.
Use Row Level Security
Enforce database-level access control with RLS policies that check auth.email(). CREATE POLICY "admin_domain_select"
ON public . registrations
FOR SELECT
TO authenticated
USING (( select auth . email ()):: text ilike '%@nj.sgadi.us' );
Validate Redirect URLs
Prevent open redirect attacks by validating all redirect paths: if ( ! path . startsWith ( '/' ) || path . includes ( '//' ) || path . includes ( ':' )) {
path = '/'
}
Set Secure Cookies
Use Secure, HttpOnly, and SameSite=Lax flags: document . cookie = [
`name=value` ,
`Path=/` ,
`SameSite=Lax` ,
`Secure` , // HTTPS only
]. join ( '; ' )
Troubleshooting
Common Issues
“redirect_uri_mismatch” error:
Verify the callback URL in Google Console exactly matches your Supabase redirect URL
Check both local (http://localhost:3000/auth/callback) and production URLs
Ensure no trailing slashes or query parameters
Session not persisting:
Check that middleware is running (see middleware.ts:4)
Verify cookies are not blocked (check DevTools → Application → Cookies)
Look for sb-*-auth-token cookies
“Invalid OAuth client” error:
Confirm Client ID and Secret in Supabase match Google Console values
Check that the Google OAuth consent screen is configured
User redirected to / instead of intended page:
Check that the rm-auth-next cookie is being set before auth
Verify cookie domain settings for production (.njrajatmahotsav.com)
Ensure cookie is not expired (10-minute TTL)
Debug Checklist
// Check if user is authenticated
const { data : { user } } = await supabase . auth . getUser ()
console . log ( 'Current user:' , user ?. email )
// Check domain restriction
import { isAdminDomainUser } from '@/lib/admin-auth'
console . log ( 'Is admin?' , isAdminDomainUser ( user ))
// Check session in cookies
// DevTools → Application → Cookies → look for:
// - sb-{project-ref}-auth-token
// - sb-{project-ref}-auth-token-code-verifier
Resources
Supabase OAuth Setup Guide Complete Supabase configuration for Google OAuth
Google OAuth Documentation Official Google OAuth 2.0 reference
Supabase Auth Documentation Supabase authentication guides and API reference