# How to Get the Current User in Supabase

- Tool: Supabase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Supabase (all plans), @supabase/supabase-js v2+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// RECOMMENDED: Verified user data (makes API call)
const { data: { user }, error } = await supabase.auth.getUser()
if (user) {
  console.log('Verified user:', user.id, user.email)
}

// FAST but UNVERIFIED: Reads from local storage/cookies
const { data: { session } } = await supabase.auth.getSession()
if (session?.user) {
  console.log('Cached user:', session.user.id, session.user.email)
}
```

**Expected result:** You understand that getUser() returns verified data and getSession() returns cached data.

### 2. 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().

```
// React component example
import { useEffect, useState } from 'react'
import { createClient } from '@supabase/supabase-js'
import type { User } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

function useCurrentUser() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchUser() {
      const { data: { user } } = await supabase.auth.getUser()
      setUser(user)
      setLoading(false)
    }
    fetchUser()

    // Listen for auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  return { user, loading }
}
```

**Expected result:** A reusable hook that returns the current verified user and updates automatically when auth state changes.

### 3. 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.

```
const { data: { user } } = await supabase.auth.getUser()

if (user) {
  // Core fields
  console.log('ID:', user.id)                    // UUID
  console.log('Email:', user.email)                // string
  console.log('Phone:', user.phone)                // string | undefined
  console.log('Created:', user.created_at)         // ISO timestamp

  // User metadata (from signup options.data or OAuth)
  console.log('Name:', user.user_metadata.full_name)
  console.log('Avatar:', user.user_metadata.avatar_url)

  // App metadata (server-set, includes provider info)
  console.log('Provider:', user.app_metadata.provider)
  console.log('Providers:', user.app_metadata.providers)

  // Fetch additional profile data from your profiles table
  const { data: profile } = await supabase
    .from('profiles')
    .select('*')
    .eq('id', user.id)
    .single()

  console.log('Profile:', profile)
}
```

**Expected result:** You can access the user's ID, email, metadata, and linked profile data.

### 4. 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.

```
// supabase/functions/get-profile/index.ts
import { createClient } from 'npm:@supabase/supabase-js@2'
import { corsHeaders } from '../_shared/cors.ts'

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    {
      global: {
        headers: { Authorization: req.headers.get('Authorization')! },
      },
    }
  )

  const { data: { user }, error } = await supabase.auth.getUser()
  if (error || !user) {
    return new Response(
      JSON.stringify({ error: 'Unauthorized' }),
      { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    )
  }

  // User is verified — fetch their profile
  const { data: profile } = await supabase
    .from('profiles')
    .select('*')
    .eq('id', user.id)
    .single()

  return new Response(
    JSON.stringify({ user: { id: user.id, email: user.email }, profile }),
    { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
  )
})
```

**Expected result:** The Edge Function verifies the user's JWT and returns their profile data, or 401 if not authenticated.

### 5. 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.

```
-- RLS policy using auth.uid()
create policy "Users read own data" on public.todos
  for select to authenticated
  using ((select auth.uid()) = user_id);

-- Database function using auth.uid()
create or replace function public.get_my_todos()
returns setof public.todos
language sql
security invoker
as $$
  select * from public.todos
  where user_id = (select auth.uid())
  order by created_at desc;
$$;

-- Access full JWT claims
select auth.jwt() ->> 'email' as email;
select auth.jwt() -> 'app_metadata' ->> 'provider' as provider;
```

**Expected result:** RLS policies and database functions correctly identify the current user via their JWT.

## Complete code example

File: `src/lib/auth.ts`

```typescript
// src/lib/auth.ts
// Reusable auth helpers for getting the current user

import { createClient } from '@supabase/supabase-js'
import type { User } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// Get the verified current user (makes API call)
export async function getCurrentUser(): Promise<User | null> {
  const { data: { user }, error } = await supabase.auth.getUser()
  if (error) {
    console.error('Auth error:', error.message)
    return null
  }
  return user
}

// Get user with profile data from the profiles table
export async function getCurrentUserWithProfile() {
  const user = await getCurrentUser()
  if (!user) return null

  const { data: profile, error } = await supabase
    .from('profiles')
    .select('*')
    .eq('id', user.id)
    .single()

  if (error) {
    console.error('Profile fetch error:', error.message)
  }

  return {
    id: user.id,
    email: user.email ?? '',
    name: user.user_metadata?.full_name ?? profile?.full_name ?? '',
    avatar: user.user_metadata?.avatar_url ?? profile?.avatar_url ?? '',
    provider: user.app_metadata?.provider ?? 'email',
    createdAt: user.created_at,
    profile,
  }
}

// Check if a user is currently signed in
export async function isAuthenticated(): Promise<boolean> {
  const user = await getCurrentUser()
  return user !== null
}

// Require authentication — throws if not signed in
export async function requireUser(): Promise<User> {
  const user = await getCurrentUser()
  if (!user) {
    throw new Error('Authentication required')
  }
  return user
}
```

## Common mistakes

- **Using getSession().user for authorization decisions on the server** — undefined Fix: 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.
- **Not handling the null case when the user is not signed in** — undefined Fix: Both getUser() and getSession() return null when no user is authenticated. Always check for null before accessing user properties to avoid runtime errors.
- **Calling getUser() on every render without caching, causing excessive API calls** — undefined Fix: Call getUser() once on mount, then use onAuthStateChange to track subsequent changes. The onAuthStateChange callback provides the session object which includes the user.
- **Assuming user_metadata always contains a full_name or avatar_url field** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-get-the-current-user-in-supabase
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-get-the-current-user-in-supabase
