To get the current user in Supabase, use supabase.auth.getUser() which makes an API call to verify the JWT and returns trusted user data. Avoid using supabase.auth.getSession() for authorization because it reads cached data from local storage without verification. On the server, always use getUser(). On the client, getUser() is recommended for security-sensitive operations, while getSession() is acceptable for non-critical UI updates.
Getting the Current User in Supabase Safely
Retrieving the current user is one of the most common operations in any Supabase application. Supabase provides two methods: getUser() and getSession(). Understanding the difference is critical for security. This tutorial explains when to use each, how to extract user data like email, metadata, and profile information, and how to build a reusable pattern for accessing the current user in both client and server contexts.
Prerequisites
- A Supabase project with Auth configured
- The @supabase/supabase-js library installed
- At least one user account created (for testing)
- Basic knowledge of JavaScript/TypeScript
Step-by-step guide
Understand getUser() vs getSession()
Understand getUser() vs getSession()
Supabase provides two methods that return user data, but they work very differently. getSession() reads the JWT from local storage (browser) or cookies (SSR) without making a network request. It is fast but unverified — the data could be stale or tampered with. getUser() makes an API call to the Supabase Auth server that cryptographically verifies the JWT and returns the latest user data. Always use getUser() when you need to make authorization decisions.
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6)78// RECOMMENDED: Verified user data (makes API call)9const { data: { user }, error } = await supabase.auth.getUser()10if (user) {11 console.log('Verified user:', user.id, user.email)12}1314// FAST but UNVERIFIED: Reads from local storage/cookies15const { data: { session } } = await supabase.auth.getSession()16if (session?.user) {17 console.log('Cached user:', session.user.id, session.user.email)18}Expected result: You understand that getUser() returns verified data and getSession() returns cached data.
Get the current user on the client side
Get the current user on the client side
In a browser context (React, Vue, Svelte, etc.), use getUser() to retrieve the verified current user. Handle the case where no user is signed in by checking for null. For UI display purposes like showing the user's name or avatar, getSession() is acceptable since the visual display is not a security boundary. For anything involving permissions or data access, use getUser().
1// React component example2import { useEffect, useState } from 'react'3import { createClient } from '@supabase/supabase-js'4import type { User } from '@supabase/supabase-js'56const supabase = createClient(7 process.env.NEXT_PUBLIC_SUPABASE_URL!,8 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!9)1011function useCurrentUser() {12 const [user, setUser] = useState<User | null>(null)13 const [loading, setLoading] = useState(true)1415 useEffect(() => {16 async function fetchUser() {17 const { data: { user } } = await supabase.auth.getUser()18 setUser(user)19 setLoading(false)20 }21 fetchUser()2223 // Listen for auth changes24 const { data: { subscription } } = supabase.auth.onAuthStateChange(25 (_event, session) => {26 setUser(session?.user ?? null)27 }28 )2930 return () => subscription.unsubscribe()31 }, [])3233 return { user, loading }34}Expected result: A reusable hook that returns the current verified user and updates automatically when auth state changes.
Access user metadata and profile information
Access user metadata and profile information
The user object contains several useful fields. The id is the user's UUID, email is their email address, and user_metadata contains custom data passed during signup or from OAuth providers (name, avatar, etc.). For additional profile data, query your profiles table using the user's ID. The app_metadata field contains provider information and is set by the server, not the user.
1const { data: { user } } = await supabase.auth.getUser()23if (user) {4 // Core fields5 console.log('ID:', user.id) // UUID6 console.log('Email:', user.email) // string7 console.log('Phone:', user.phone) // string | undefined8 console.log('Created:', user.created_at) // ISO timestamp910 // User metadata (from signup options.data or OAuth)11 console.log('Name:', user.user_metadata.full_name)12 console.log('Avatar:', user.user_metadata.avatar_url)1314 // App metadata (server-set, includes provider info)15 console.log('Provider:', user.app_metadata.provider)16 console.log('Providers:', user.app_metadata.providers)1718 // Fetch additional profile data from your profiles table19 const { data: profile } = await supabase20 .from('profiles')21 .select('*')22 .eq('id', user.id)23 .single()2425 console.log('Profile:', profile)26}Expected result: You can access the user's ID, email, metadata, and linked profile data.
Get the user in an Edge Function
Get the user in an Edge Function
In Supabase Edge Functions, the user's JWT is sent via the Authorization header. Create a Supabase client inside the function and pass the Authorization header from the request. Then call getUser() to verify the token. The Edge Function environment automatically has SUPABASE_URL and SUPABASE_ANON_KEY available.
1// supabase/functions/get-profile/index.ts2import { createClient } from 'npm:@supabase/supabase-js@2'3import { corsHeaders } from '../_shared/cors.ts'45Deno.serve(async (req) => {6 if (req.method === 'OPTIONS') {7 return new Response('ok', { headers: corsHeaders })8 }910 const supabase = createClient(11 Deno.env.get('SUPABASE_URL')!,12 Deno.env.get('SUPABASE_ANON_KEY')!,13 {14 global: {15 headers: { Authorization: req.headers.get('Authorization')! },16 },17 }18 )1920 const { data: { user }, error } = await supabase.auth.getUser()21 if (error || !user) {22 return new Response(23 JSON.stringify({ error: 'Unauthorized' }),24 { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }25 )26 }2728 // User is verified — fetch their profile29 const { data: profile } = await supabase30 .from('profiles')31 .select('*')32 .eq('id', user.id)33 .single()3435 return new Response(36 JSON.stringify({ user: { id: user.id, email: user.email }, profile }),37 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }38 )39})Expected result: The Edge Function verifies the user's JWT and returns their profile data, or 401 if not authenticated.
Use auth.uid() in RLS policies and SQL
Use auth.uid() in RLS policies and SQL
In SQL contexts like RLS policies, database functions, and the SQL Editor, use the auth.uid() helper to get the current user's UUID. This extracts the sub claim from the JWT. For more detailed JWT claims, use auth.jwt(). These functions are available in any SQL context where a Supabase client made the request.
1-- RLS policy using auth.uid()2create policy "Users read own data" on public.todos3 for select to authenticated4 using ((select auth.uid()) = user_id);56-- Database function using auth.uid()7create or replace function public.get_my_todos()8returns setof public.todos9language sql10security invoker11as $$12 select * from public.todos13 where user_id = (select auth.uid())14 order by created_at desc;15$$;1617-- Access full JWT claims18select auth.jwt() ->> 'email' as email;19select auth.jwt() -> 'app_metadata' ->> 'provider' as provider;Expected result: RLS policies and database functions correctly identify the current user via their JWT.
Complete working example
1// src/lib/auth.ts2// Reusable auth helpers for getting the current user34import { createClient } from '@supabase/supabase-js'5import type { User } from '@supabase/supabase-js'67const supabase = createClient(8 process.env.NEXT_PUBLIC_SUPABASE_URL!,9 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!10)1112// Get the verified current user (makes API call)13export async function getCurrentUser(): Promise<User | null> {14 const { data: { user }, error } = await supabase.auth.getUser()15 if (error) {16 console.error('Auth error:', error.message)17 return null18 }19 return user20}2122// Get user with profile data from the profiles table23export async function getCurrentUserWithProfile() {24 const user = await getCurrentUser()25 if (!user) return null2627 const { data: profile, error } = await supabase28 .from('profiles')29 .select('*')30 .eq('id', user.id)31 .single()3233 if (error) {34 console.error('Profile fetch error:', error.message)35 }3637 return {38 id: user.id,39 email: user.email ?? '',40 name: user.user_metadata?.full_name ?? profile?.full_name ?? '',41 avatar: user.user_metadata?.avatar_url ?? profile?.avatar_url ?? '',42 provider: user.app_metadata?.provider ?? 'email',43 createdAt: user.created_at,44 profile,45 }46}4748// Check if a user is currently signed in49export async function isAuthenticated(): Promise<boolean> {50 const user = await getCurrentUser()51 return user !== null52}5354// Require authentication — throws if not signed in55export async function requireUser(): Promise<User> {56 const user = await getCurrentUser()57 if (!user) {58 throw new Error('Authentication required')59 }60 return user61}Common mistakes when getting the Current User in Supabase
Why it's a problem: Using getSession().user for authorization decisions on the server
How to avoid: Always use getUser() on the server. getSession() reads cached data without JWT verification, so a tampered token would pass. getUser() verifies the JWT with the Supabase Auth server.
Why it's a problem: Not handling the null case when the user is not signed in
How to avoid: Both getUser() and getSession() return null when no user is authenticated. Always check for null before accessing user properties to avoid runtime errors.
Why it's a problem: Calling getUser() on every render without caching, causing excessive API calls
How to avoid: Call getUser() once on mount, then use onAuthStateChange to track subsequent changes. The onAuthStateChange callback provides the session object which includes the user.
Why it's a problem: Assuming user_metadata always contains a full_name or avatar_url field
How to avoid: OAuth providers set different metadata fields. Always use optional chaining (user.user_metadata?.full_name) and provide fallback values for missing fields.
Best practices
- Use getUser() for all authorization decisions — it is the only method that verifies the JWT
- Use getSession() only for non-critical client-side UI updates where speed matters more than verification
- Combine getUser() with onAuthStateChange for initial verification plus real-time session tracking
- Create reusable helper functions (getCurrentUser, requireUser) to avoid repeating auth logic
- Always handle the null/error case when the user is not signed in
- Access additional user data from a profiles table rather than relying solely on user_metadata
- Wrap auth.uid() with (select auth.uid()) in RLS policies for better query performance
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to get the current user in my Supabase app. Explain the difference between getUser() and getSession(), show me how to build a React hook that tracks the current user, and how to access the user's metadata and profile data.
Create reusable auth helper functions for my Supabase project: getCurrentUser (verified), getCurrentUserWithProfile (includes profile table data), isAuthenticated (boolean check), and requireUser (throws if not signed in). Use getUser() for all verification.
Frequently asked questions
What is the difference between getUser() and getSession()?
getUser() makes an API call to the Supabase Auth server to verify the JWT and returns trusted user data. getSession() reads the session from local storage or cookies without verification. Always use getUser() for authorization decisions.
Is getUser() slow because it makes an API call?
It adds about 20-50ms of latency compared to getSession(). This is negligible for most use cases and is the cost of security. For non-critical UI updates, getSession() is acceptable.
How do I get the user's display name from OAuth?
OAuth providers store the name in user.user_metadata.full_name (Google) or user.user_metadata.name (GitHub). Always use optional chaining since different providers use different field names.
Can I get the current user in a database function?
Yes, use auth.uid() to get the user's UUID in SQL. This works in RLS policies, database functions, and triggers when the request was made via the Supabase client with a valid JWT.
What happens if the JWT is expired when I call getUser()?
The Supabase client automatically refreshes expired JWTs before making the getUser() call. If the refresh token is also expired, getUser() returns an error and the user needs to sign in again.
How do I update the current user's metadata?
Use supabase.auth.updateUser({ data: { full_name: 'New Name' } }) to update user_metadata. For profile table data, use a regular UPDATE query: supabase.from('profiles').update({ full_name: 'New Name' }).eq('id', user.id).
Can RapidDev help implement user management in my Supabase app?
Yes, RapidDev can build complete user management flows including authentication, profile pages, role-based access, and admin user management dashboards for your Supabase application.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation