Skip to content

A comprehensive Next.js 15 + TypeScript guide demonstrating JWT authentication patterns with Supabase. Features include JWT token creation & validation, protected API routes, automatic token refresh, role-based access control, and security best practices.

Notifications You must be signed in to change notification settings

devpayoub/JWT-Token-Management-with-Supabase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ” JWT Token Management with Supabase

A comprehensive guide and example project demonstrating how to create, manage, and secure JWT tokens with Next.js 15, TypeScript, and Supabase. This project shows various authentication patterns including token creation, validation, refresh, and secure storage.

✨ Features

  • JWT Token Creation - Generate secure tokens with Supabase Auth
  • Token Validation - Verify tokens on both client and server
  • Token Refresh - Automatic token renewal
  • Secure Storage - Browser and server-side token management
  • Role-based Access - JWT claims for authorization
  • Token Revocation - Secure logout and token invalidation
  • Middleware Protection - Route protection with JWT verification
  • Type Safety - Full TypeScript integration
  • Error Handling - Comprehensive authentication error management

πŸš€ Tech Stack

  • Frontend: Next.js 15, React 19, TypeScript
  • Backend: Next.js API Routes, Server Actions
  • Authentication: Supabase Auth with JWT
  • Database: Supabase PostgreSQL
  • Styling: Tailwind CSS 4
  • Deployment: Vercel-ready

πŸ“‹ Prerequisites

Before running this project, make sure you have:

  • Node.js 18+ installed
  • A Supabase account and project
  • Git installed

πŸ› οΈ Installation

  1. Clone the repository

    git clone https://github.com/devpayoub/JWT-Token-Management-with-Supabase.git
    cd nextjs-supabase-api-guide
  2. Install dependencies

    npm install
  3. Set up environment variables

    Create a .env.local file in the root directory:

    NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
    SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
    JWT_SECRET=your_jwt_secret_key

    To get these values:

    • Go to your Supabase project dashboard
    • Navigate to Settings β†’ API
    • Copy the "Project URL", "anon public" key, and "service_role" key
    • Generate a JWT secret for additional security
  4. Set up the database

    In your Supabase dashboard, create a Users table with the following SQL:

    CREATE TABLE Users (
      id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
      email TEXT UNIQUE NOT NULL,
      username TEXT UNIQUE NOT NULL,
      role TEXT DEFAULT 'user',
      created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
    );
  5. Run the development server

    npm run dev
  6. Open your browser

    Navigate to http://localhost:3000

πŸ“ Project Structure

src/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/                    # Next.js API Routes
β”‚   β”‚   β”œβ”€β”€ auth/              # Authentication endpoints
β”‚   β”‚   β”‚   β”œβ”€β”€ login/         # POST /api/auth/login
β”‚   β”‚   β”‚   └── verify/        # POST /api/auth/verify
β”‚   β”‚   └── protected/         # Protected API routes
β”‚   β”‚       └── users/         # GET /api/protected/users
β”‚   β”œβ”€β”€ actions/               # Server Actions
β”‚   β”‚   └── auth.ts            # Authentication actions
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ supabase.ts        # Supabase client
β”‚   β”‚   └── jwt.ts             # JWT utilities
β”‚   β”œβ”€β”€ middleware.ts          # Route protection
β”‚   β”œβ”€β”€ layout.tsx             # Root layout
β”‚   β”œβ”€β”€ page.tsx               # Main interface
β”‚   └── globals.css            # Global styles

πŸ” JWT Token Management Patterns

1. Token Creation & Storage

Client-side Token Management:

// Login and get JWT token
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'password'
})

// Access token is automatically stored in localStorage
const accessToken = data.session?.access_token
const refreshToken = data.session?.refresh_token

Server-side Token Validation:

// Verify JWT token on server
import { createClient } from '@supabase/supabase-js'

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

const { data: { user }, error } = await supabase.auth.getUser(token)

2. Protected Routes

API Route Protection:

// src/app/api/protected/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/jwt'

export async function GET(request: NextRequest) {
  try {
    const token = request.headers.get('authorization')?.replace('Bearer ', '')
    
    if (!token) {
      return NextResponse.json(
        { error: 'No token provided' },
        { status: 401 }
      )
    }

    const user = await verifyToken(token)
    if (!user) {
      return NextResponse.json(
        { error: 'Invalid token' },
        { status: 401 }
      )
    }

    // Your protected logic here
    return NextResponse.json({ message: 'Protected data' })
  } catch (error) {
    return NextResponse.json(
      { error: 'Authentication failed' },
      { status: 401 }
    )
  }
}

Middleware Protection:

// src/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()

  // Protect routes that require authentication
  if (!session && req.nextUrl.pathname.startsWith('/protected')) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  // Redirect authenticated users away from auth pages
  if (session && (req.nextUrl.pathname === '/login' || req.nextUrl.pathname === '/register')) {
    return NextResponse.redirect(new URL('/', req.url))
  }

  return res
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

3. Token Refresh

Automatic Token Refresh:

// src/lib/auth-helpers.ts
import { supabase } from './supabase'

export const refreshToken = async () => {
  const { data, error } = await supabase.auth.refreshSession()
  
  if (error) {
    // Handle refresh error
    await supabase.auth.signOut()
    return null
  }
  
  return data.session
}

// Set up automatic token refresh
supabase.auth.onAuthStateChange(async (event, session) => {
  if (event === 'TOKEN_REFRESHED') {
    // Token was automatically refreshed
    console.log('Token refreshed successfully')
  }
})

4. Custom JWT Claims

Adding Custom Claims:

// Server-side: Add custom claims to JWT
const { data, error } = await supabase.auth.admin.updateUserById(
  userId,
  {
    user_metadata: {
      role: 'admin',
      permissions: ['read', 'write', 'delete']
    }
  }
)

Using Custom Claims:

// Client-side: Access custom claims
const { data: { user } } = await supabase.auth.getUser()
const userRole = user?.user_metadata?.role
const permissions = user?.user_metadata?.permissions

πŸ› οΈ Implementation Examples

Authentication API Routes

Login Endpoint:

// src/app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { supabase } from '@/lib/supabase'

export async function POST(request: NextRequest) {
  try {
    const { email, password } = await request.json()

    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    })

    if (error) {
      return NextResponse.json(
        { error: error.message },
        { status: 401 }
      )
    }

    return NextResponse.json({
      user: data.user,
      session: data.session,
      message: 'Login successful'
    })
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

Token Verification Endpoint:

// src/app/api/auth/verify/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { supabase } from '@/lib/supabase'

export async function POST(request: NextRequest) {
  try {
    const { token } = await request.json()

    const { data: { user }, error } = await supabase.auth.getUser(token)

    if (error || !user) {
      return NextResponse.json(
        { error: 'Invalid token' },
        { status: 401 }
      )
    }

    return NextResponse.json({
      user,
      valid: true
    })
  } catch (error) {
    return NextResponse.json(
      { error: 'Token verification failed' },
      { status: 500 }
    )
  }
}

Server Actions for Authentication

// src/app/actions/auth.ts
'use server'

import { supabase } from '@/lib/supabase'
import { revalidatePath } from 'next/cache'

export async function loginUser(formData: FormData) {
  try {
    const email = formData.get('email') as string
    const password = formData.get('password') as string

    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    })

    if (error) throw new Error(error.message)

    revalidatePath('/')
    return { success: true, user: data.user }
  } catch (error) {
    return { success: false, error: error.message }
  }
}

export async function logoutUser() {
  try {
    const { error } = await supabase.auth.signOut()
    
    if (error) throw new Error(error.message)

    revalidatePath('/')
    return { success: true }
  } catch (error) {
    return { success: false, error: error.message }
  }
}

πŸ”’ Security Features

  • JWT Token Validation - Server-side token verification
  • Automatic Token Refresh - Seamless session management
  • Secure Token Storage - HttpOnly cookies and secure localStorage
  • Role-based Authorization - Custom JWT claims for permissions
  • Token Revocation - Secure logout and session cleanup
  • CSRF Protection - Built-in Supabase security
  • Rate Limiting - Prevent brute force attacks

πŸš€ Advanced JWT Patterns

1. Custom JWT Claims

// Add custom claims to user metadata
const { data, error } = await supabase.auth.updateUser({
  data: {
    role: 'admin',
    permissions: ['read', 'write', 'delete'],
    organization: 'acme-corp'
  }
})

2. Token Refresh Strategy

// Automatic token refresh before expiration
useEffect(() => {
  const {
    data: { subscription },
  } = supabase.auth.onAuthStateChange(async (event, session) => {
    if (event === 'TOKEN_REFRESHED') {
      console.log('Token refreshed:', session?.access_token)
    }
  })

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

3. Role-based Route Protection

// Check user role before allowing access
const checkUserRole = async (requiredRole: string) => {
  const { data: { user } } = await supabase.auth.getUser()
  const userRole = user?.user_metadata?.role
  
  return userRole === requiredRole
}

πŸ§ͺ Testing JWT Authentication

Using cURL

# Login and get token
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"password"}'

# Use token for protected request
curl http://localhost:3000/api/protected/users \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# Verify token
curl -X POST http://localhost:3000/api/auth/verify \
  -H "Content-Type: application/json" \
  -d '{"token":"YOUR_JWT_TOKEN"}'

πŸš€ Deployment

Environment Variables for Production

NEXT_PUBLIC_SUPABASE_URL=your_production_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_production_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_production_service_role_key
JWT_SECRET=your_production_jwt_secret

Security Considerations

  • Use HTTPS in production
  • Set secure cookie flags
  • Implement rate limiting
  • Monitor token usage
  • Regular security audits

πŸ“š Learning Resources

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ™ Acknowledgments

  • Next.js - The React framework
  • Supabase - The open source Firebase alternative
  • Vercel - The platform for frontend developers

πŸ“ž Support

If you have any questions or need help with this project:

  1. Check the Issues page
  2. Create a new issue with a detailed description
  3. Include your environment details and error messages

About

A comprehensive Next.js 15 + TypeScript guide demonstrating JWT authentication patterns with Supabase. Features include JWT token creation & validation, protected API routes, automatic token refresh, role-based access control, and security best practices.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published