Skip to main content

Authentication & Authorization

TalentG implements a robust authentication and authorization system using Supabase Auth with OAuth integration, JWT tokens, and role-based access control (RBAC).

Authentication Architecture

Supported Authentication Methods

1. Email/Password Authentication

Traditional email and password authentication with additional security features. Features:
  • Email verification required for account activation
  • Password reset via secure email links
  • Account lockout protection after failed attempts
  • Password strength requirements and validation

2. OAuth Providers

Social authentication through trusted OAuth providers. Supported Providers:
  • Google OAuth: Primary authentication method
  • GitHub OAuth: Developer-focused authentication
  • Future: LinkedIn, Microsoft (planned)
Passwordless authentication via email magic links. Process:
  1. User enters email address
  2. System sends secure magic link
  3. Link contains encrypted authentication token
  4. Single-use token with expiration (24 hours)

Authentication Flow

Token Management

JWT Token Structure

Access Token:
{
  "aud": "authenticated",
  "exp": 1640995200,
  "iat": 1640991600,
  "iss": "https://your-project.supabase.co/auth/v1",
  "sub": "user-uuid",
  "email": "user@example.com",
  "app_metadata": {
    "provider": "google",
    "providers": ["google"]
  },
  "user_metadata": {
    "full_name": "John Doe",
    "avatar_url": "https://..."
  },
  "role": "authenticated"
}
Refresh Token:
  • Used to obtain new access tokens
  • Stored securely in HTTP-only cookies
  • Automatic rotation on token refresh

Token Lifecycle

Token Expiration

  • Access Token: 1 hour expiration
  • Refresh Token: 30 days expiration (configurable)
  • Automatic Refresh: Client-side token refresh before expiration

Token Storage

// Secure token storage
const auth = {
  accessToken: localStorage.getItem('supabase.auth.token'),
  refreshToken: getCookie('supabase-auth-refresh-token'),
  expiresAt: localStorage.getItem('supabase.auth.expires_at')
};

Token Refresh Logic

async function refreshTokenIfNeeded() {
  const expiresAt = localStorage.getItem('supabase.auth.expires_at');
  const now = Math.floor(Date.now() / 1000);

  if (expiresAt && now > parseInt(expiresAt) - 300) { // 5 min buffer
    const { data, error } = await supabase.auth.refreshSession();
    if (error) {
      // Redirect to login
      window.location.href = '/auth/login';
    }
  }
}

Role-Based Access Control (RBAC)

User Roles Hierarchy

RoleLevelDescriptionDashboard Access
Free User1Basic platform accessGeneral dashboard
Paid User2Premium AI featuresAll free + Pro features
Course Learner3Course-based learningLearning dashboard
Intern4Internship managementIntern dashboard
Course Mentor5Teaching and mentoringMentor dashboard
Internship Mentor6Advanced mentorshipInternship mentor dashboard
Trainer7Training program managementTraining dashboard
Admin8System administrationAdmin panel
TalentGro Team9Internal managementEnhanced admin
University Student3Academic managementStudent portal

Permission System

Permission Categories

System Permissions:
  • read:users - View user information
  • write:users - Modify user accounts
  • delete:users - Deactivate user accounts
  • admin:system - Full system administration
Content Permissions:
  • create:content - Create courses and materials
  • edit:content - Modify existing content
  • publish:content - Publish content for users
  • delete:content - Remove content
Assessment Permissions:
  • take:assessment - Access assessment features
  • view:results - View assessment results
  • export:results - Export assessment reports
  • manage:assessments - Admin assessment management
Learning Permissions:
  • enroll:courses - Enroll in courses
  • submit:assignments - Submit assignments
  • grade:assignments - Grade student work
  • manage:classes - Manage class schedules

Permission Mapping

const ROLE_PERMISSIONS = {
  free_user: [
    'read:own_profile',
    'take:assessment',
    'view:own_results',
    'read:announcements'
  ],
  paid_user: [
    'read:own_profile',
    'take:assessment',
    'view:own_results',
    'use:ai_features',
    'export:results',
    'read:announcements'
  ],
  admin: [
    'read:users',
    'write:users',
    'delete:users',
    'admin:system',
    'create:content',
    'edit:content',
    'publish:content',
    'delete:content',
    'manage:assessments',
    'manage:classes'
  ]
};

Route Protection

Middleware Implementation

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient({ req, res });

  const {
    data: { session },
  } = await supabase.auth.getSession();

  // Public routes - no authentication required
  const publicRoutes = [
    '/',
    '/auth/login',
    '/auth/signup',
    '/auth/forgot-password',
    '/announcements',
    '/shared'
  ];

  const isPublicRoute = publicRoutes.some(route =>
    req.nextUrl.pathname.startsWith(route)
  );

  if (isPublicRoute) {
    return res;
  }

  // Require authentication for all other routes
  if (!session) {
    const redirectUrl = new URL('/auth/login', req.url);
    redirectUrl.searchParams.set('redirectTo', req.nextUrl.pathname);
    return NextResponse.redirect(redirectUrl);
  }

  // Role-based route protection
  const routePermissions = {
    '/admin': ['admin', 'talentgro_team'],
    '/intern-dashboard': ['intern'],
    '/mentor-dashboard': ['course_mentor'],
    '/internship-mentor': ['internship_mentor'],
    '/learner-dashboard': ['course_learner'],
    '/student-layout': ['university_student'],
    '/ai-resume-chat': ['paid_user'],
    '/interview-questions': ['paid_user']
  };

  for (const [route, allowedRoles] of Object.entries(routePermissions)) {
    if (req.nextUrl.pathname.startsWith(route)) {
      const userRole = session.user.user_metadata.role;
      if (!allowedRoles.includes(userRole)) {
        return NextResponse.redirect(new URL('/dashboard', req.url));
      }
    }
  }

  return res;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Client-Side Route Protection

// components/ProtectedRoute.tsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '@/hooks/useAuth';

interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRoles?: string[];
  requiredPermissions?: string[];
}

export function ProtectedRoute({
  children,
  requiredRoles = [],
  requiredPermissions = []
}: ProtectedRouteProps) {
  const { user, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (loading) return;

    if (!user) {
      router.push('/auth/login');
      return;
    }

    // Check role requirements
    if (requiredRoles.length > 0) {
      const hasRequiredRole = requiredRoles.some(role =>
        user.roles.includes(role)
      );
      if (!hasRequiredRole) {
        router.push('/dashboard');
        return;
      }
    }

    // Check permission requirements
    if (requiredPermissions.length > 0) {
      const hasRequiredPermissions = requiredPermissions.every(permission =>
        user.permissions.includes(permission)
      );
      if (!hasRequiredPermissions) {
        router.push('/dashboard');
        return;
      }
    }
  }, [user, loading, requiredRoles, requiredPermissions, router]);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!user) {
    return null;
  }

  return <>{children}</>;
}

Database Security

Row Level Security (RLS) Policies

-- Enable RLS on all tables
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.strength_finder_assessments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.courses ENABLE ROW LEVEL SECURITY;

-- User can view own profile
CREATE POLICY "Users can view own profile"
  ON public.profiles FOR SELECT
  USING (auth.uid() = id);

-- Users can view own assessments
CREATE POLICY "Users can access own assessments"
  ON public.strength_finder_assessments FOR ALL
  USING (auth.uid() = user_id);

-- Admins can access all data
CREATE POLICY "Admins can access all user data"
  ON public.profiles FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM public.profiles
      WHERE id = auth.uid()
      AND role IN ('admin', 'talentgro_team')
    )
  );

-- Course mentors can view enrolled students
CREATE POLICY "Mentors can view course students"
  ON public.enrollments FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM public.courses
      WHERE courses.id = enrollments.course_id
      AND courses.mentor_id = auth.uid()
    )
  );

Data Isolation

User Data Isolation

  • Complete separation between user data
  • Users can only access their own information
  • Cross-user data leakage prevention

Organization Data Isolation

  • University-specific data isolation
  • Trainer-specific course access
  • Department-level data segmentation

API Authentication

Bearer Token Authentication

// API route authentication
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';

export async function GET(request: Request) {
  const supabase = createRouteHandlerClient({ cookies });

  const {
    data: { session },
    error
  } = await supabase.auth.getSession();

  if (error || !session) {
    return Response.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  // User is authenticated, proceed with request
  const userId = session.user.id;

  // Continue with API logic...
}

API Key Authentication (Admin)

// Admin API key validation
const ADMIN_API_KEY = process.env.ADMIN_API_KEY;

export async function adminEndpoint(request: Request) {
  const apiKey = request.headers.get('X-API-Key');

  if (apiKey !== ADMIN_API_KEY) {
    return Response.json(
      { error: 'Invalid API key' },
      { status: 401 }
    );
  }

  // Proceed with admin operation...
}

Session Management

Session Lifecycle

Session Creation

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password'
});

Session Validation

const { data: { session }, error } = await supabase.auth.getSession();
if (!session) {
  // Redirect to login
}

Session Termination

const { error } = await supabase.auth.signOut();
// Clear local session data
localStorage.removeItem('supabase.auth.token');

Session Security

// Next.js configuration
export default {
  cookies: {
    sessionToken: {
      name: 'supabase-auth-token',
      options: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax',
        maxAge: 60 * 60 * 24 * 7 // 7 days
      }
    }
  }
};

Session Monitoring

  • Automatic session refresh before expiration
  • Session invalidation on suspicious activity
  • Concurrent session management
  • Session timeout configuration

Multi-Factor Authentication (Future)

Planned MFA Implementation

TOTP (Time-based One-Time Password)

  • Google Authenticator integration
  • SMS-based verification
  • Email-based secondary verification

MFA Flow

// MFA verification
const { data, error } = await supabase.auth.mfa.verify({
  factorId: 'totp-factor-id',
  code: '123456'
});

Security Best Practices

Password Security

  • Minimum 8 characters with complexity requirements
  • Password history prevention
  • Secure password reset flow
  • Brute force protection

API Security

  • Rate limiting on all endpoints
  • Input validation and sanitization
  • CORS configuration
  • Security headers implementation

Audit Logging

-- Audit log table
CREATE TABLE audit_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id),
  action VARCHAR(255) NOT NULL,
  resource_type VARCHAR(100),
  resource_id UUID,
  old_values JSONB,
  new_values JSONB,
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Insert audit log function
CREATE OR REPLACE FUNCTION audit_log_changes()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO audit_logs (
    user_id,
    action,
    resource_type,
    resource_id,
    old_values,
    new_values
  ) VALUES (
    auth.uid(),
    TG_OP,
    TG_TABLE_NAME,
    COALESCE(NEW.id, OLD.id),
    CASE WHEN TG_OP = 'DELETE' THEN row_to_json(OLD) ELSE NULL END,
    CASE WHEN TG_OP != 'DELETE' THEN row_to_json(NEW) ELSE NULL END
  );
  RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- Attach audit trigger
CREATE TRIGGER audit_users_changes
  AFTER INSERT OR UPDATE OR DELETE ON public.profiles
  FOR EACH ROW EXECUTE FUNCTION audit_log_changes();

Testing Authentication

Unit Tests

describe('Authentication', () => {
  test('validates JWT tokens correctly', async () => {
    const token = generateValidToken();
    const result = await validateToken(token);
    expect(result.valid).toBe(true);
  });

  test('rejects expired tokens', async () => {
    const expiredToken = generateExpiredToken();
    const result = await validateToken(expiredToken);
    expect(result.valid).toBe(false);
  });

  test('enforces role-based access control', async () => {
    const user = createTestUser('student');
    const result = await checkPermission(user, 'admin:access');
    expect(result.allowed).toBe(false);
  });
});

Integration Tests

describe('Authentication Flow', () => {
  test('complete login flow works', async () => {
    // Register user
    await request(app)
      .post('/api/auth/register')
      .send({ email: 'test@example.com', password: 'password123' })
      .expect(201);

    // Login
    const loginResponse = await request(app)
      .post('/api/auth/login')
      .send({ email: 'test@example.com', password: 'password123' })
      .expect(200);

    const token = loginResponse.body.token;

    // Access protected route
    await request(app)
      .get('/api/protected')
      .set('Authorization', `Bearer ${token}`)
      .expect(200);
  });
});
This comprehensive authentication and authorization system ensures secure, scalable access control for TalentG’s multi-role platform.