Authentication in the BAP Beta Tau frontend is a two-stage process. The first stage is identity verification: the user signs in with Google through Supabase, which establishes a session and stores a JWT access token inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/asubap/website/llms.txt
Use this file to discover all available pages before exploring further.
localStorage. The second stage is authorization: the application takes that JWT and sends it to the backend’s POST /users endpoint along with the user’s email, receiving back a role object that determines which pages the user can access. Both stages must succeed for isAuthenticated to be true. This separation of concerns — Supabase owns identity, the backend owns authorization — means the frontend never queries the database directly and all member data is gated by the backend’s own access control logic.
The Auth Flow, End to End
Supabase Client Initialization
The Supabase client is created once insrc/context/auth/supabaseClient.ts and imported wherever auth operations are needed:
storage: localStorage means the Supabase session (including the JWT access token and refresh token) survives browser tab closures and page refreshes. On the next visit, getSession() returns the cached session immediately, so the user does not have to re-authenticate.
The AuthProvider Component
AuthProvider is the top-level context provider that wraps the entire application. It manages four pieces of state: session, role, loading, and authError.
Initialization Sequence
When the app first mounts,AuthProvider runs initializeAuth() inside a useEffect:
- Calls
supabase.auth.getSession()to check for an existing session inlocalStorage. - Sets
sessionimmediately so components can observe it. - If a session exists and
user.emailis present, callsfetchUserRole(access_token, email). - If no session is found, sets
loading = false— the app is ready to show the login page.
Auth State Change Listener
Supabase firesonAuthStateChange whenever the session changes — on sign-in, sign-out, or token refresh. The AuthProvider subscribes to this event and re-fetches the role whenever a new session is detected:
Role Fetching: POST /users
The fetchUserRole function is the bridge between Supabase’s identity layer and the backend’s authorization layer:
POST /users is set directly as the role value. The shape depends on the role type (see Role Types below).
On failure: role is set to null and authError receives the error message from the backend. Importantly, the session is not immediately cleared on failure — the AuthHome page renders first and displays the error, giving the user context before signing them out.
30-Second Role Revalidation
TheAuthProvider runs a setInterval that silently re-fetches the user’s role from the backend every 30 seconds while the app is open:
- Revocation detection: If an e-board officer removes a member’s access in the admin panel, the frontend will reflect that change within 30 seconds without requiring a page refresh.
- Token freshness: The Supabase
getSession()call also handles JWT refresh under the hood, so the access token passed to the backend remains valid.
isAuthenticated Computation
| Condition | Meaning |
|---|---|
!!session | A valid Supabase session exists (the user has completed Google OAuth). |
!!role | The backend POST /users call returned a successful role payload. |
!authError | No error occurred during the role fetch (e.g., user not found in backend DB). |
isAuthenticated is exposed through the useAuth() hook and is the value read by Navbar to determine which nav links to show.
The useAuth() Hook
Any component inside AuthProvider can access auth state by calling useAuth():
Role Types
Roles are stored in the React context as theRoleType union:
e-board
String role. Full administrative access. Unlocks the
/admin dashboard with member, event, announcement, sponsor, resource, and e-board management.general-member
String role. Standard member access. Unlocks
/member (personal dashboard), all networking pages, events detail, and resources. Feature access is further gated by rank via canAccessFeature().sponsor
Object role (
SponsorRole). Unlocks /sponsor (sponsor portal). Also carries companyName so the portal can display the sponsor’s company name without an additional API call.checkRole helper in ProtectedRoute handles both shapes:
Rank-Based Feature Access
Within thegeneral-member role, access to specific features is further restricted by a member’s rank field (returned as part of the member profile from the backend, not from POST /users). The canAccessFeature utility in src/utils/permissions.ts centralizes this logic:
| Rank | event-rsvp | event-checkin | announcements | slack-access |
|---|---|---|---|---|
pledge | ✅ | ✅ | ✅ | ✅ |
inducted | ✅ | ✅ | ✅ | ✅ |
alumni | ❌ | ❌ | ❌ | ❌ |
Sign-Out Flow
Sign-out is handled by theLogOut component (src/components/logOut/LogOut.tsx), which calls supabase.auth.signOut(). Supabase clears the session from localStorage and fires onAuthStateChange with newSession = null. The AuthProvider listener then sets session = null, role = null, and loading = false. Because isAuthenticated becomes false, the Navbar re-renders with the public nav links, and any protected route the user is on will redirect them to /login on the next render.