JWT Authentication Flow Guide with Supabase Auth & Licensing
Purpose: Complete JWT authentication implementation guide for Sasha Studio with Supabase Auth and subscription licensing model
Target Users: Development team, security architects, backend developers
Overview
This guide provides comprehensive implementation details for JWT-based authentication in Sasha Studio, leveraging Supabase Auth for user management and implementing a sophisticated licensing model for subscription-based access control.
Related Guides:
- Supabase Complete Setup Guide - Supabase infrastructure
- Sasha Studio V1 Security Guide - Security hardening
- Multi-Tenant Architecture Guide - Workspace management
Authentication Architecture
JWT Token Structure & Flow
Access Token Payload
{
"aud": "authenticated",
"exp": 1704123456,
"iat": 1704122556,
"iss": "https://your-project.supabase.co/auth/v1",
"sub": "user-uuid-here",
"email": "user@example.com",
"phone": "",
"app_metadata": {
"provider": "email",
"providers": ["email"]
},
"user_metadata": {
"full_name": "John Doe",
"avatar_url": "https://example.com/avatar.jpg"
},
"role": "authenticated",
"aal": "aal1",
"amr": [{"method": "password", "timestamp": 1704122556}],
"session_id": "session-uuid-here",
// Custom Sasha Studio Claims
"workspace_id": "workspace-uuid",
"subscription_tier": "pro",
"license_status": "active",
"feature_flags": {
"ai_models": ["claude-3", "gpt-4"],
"storage_gb": 100,
"monthly_queries": 10000,
"team_members": 25
},
"permissions": [
"chat:create",
"file:upload",
"guide:access",
"publish:create",
"admin:workspace"
]
}
Refresh Token Flow
Licensing Model Implementation
Database Schema
-- Enhanced Users Table (extends Supabase auth.users)
CREATE TABLE public.user_profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
full_name TEXT,
avatar_url TEXT,
workspace_id UUID REFERENCES workspaces(id),
role workspace_role DEFAULT 'member',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Workspaces (Multi-tenant Organizations)
CREATE TABLE public.workspaces (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
owner_id UUID REFERENCES auth.users(id) NOT NULL,
subscription_id UUID REFERENCES subscriptions(id),
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Subscription Management
CREATE TABLE public.subscriptions (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
workspace_id UUID REFERENCES workspaces(id) ON DELETE CASCADE,
tier subscription_tier NOT NULL DEFAULT 'free',
status subscription_status NOT NULL DEFAULT 'active',
current_period_start TIMESTAMPTZ NOT NULL,
current_period_end TIMESTAMPTZ NOT NULL,
stripe_subscription_id TEXT,
stripe_customer_id TEXT,
billing_email TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Feature Limits by Subscription Tier
CREATE TABLE public.subscription_limits (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tier subscription_tier NOT NULL,
feature_name TEXT NOT NULL,
limit_value INTEGER NOT NULL,
limit_type limit_type NOT NULL DEFAULT 'monthly',
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tier, feature_name)
);
-- Usage Tracking
CREATE TABLE public.usage_records (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
workspace_id UUID REFERENCES workspaces(id) ON DELETE CASCADE,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
feature_name TEXT NOT NULL,
usage_count INTEGER DEFAULT 1,
metadata JSONB DEFAULT '{}',
recorded_at TIMESTAMPTZ DEFAULT NOW(),
period_start TIMESTAMPTZ NOT NULL,
period_end TIMESTAMPTZ NOT NULL
);
-- Custom Types
CREATE TYPE subscription_tier AS ENUM ('free', 'starter', 'pro', 'enterprise');
CREATE TYPE subscription_status AS ENUM ('active', 'trialing', 'past_due', 'canceled', 'unpaid');
CREATE TYPE workspace_role AS ENUM ('owner', 'admin', 'member', 'viewer');
CREATE TYPE limit_type AS ENUM ('daily', 'monthly', 'total');
Row Level Security Policies
-- Users can only access their own profile
CREATE POLICY "Users can access own profile" ON user_profiles
FOR ALL USING (auth.uid() = id);
-- Workspace access based on membership
CREATE POLICY "Workspace members can access workspace" ON workspaces
FOR SELECT USING (
id IN (
SELECT workspace_id FROM user_profiles
WHERE id = auth.uid()
)
);
-- Subscription access for workspace owners/admins
CREATE POLICY "Workspace admins can access subscription" ON subscriptions
FOR ALL USING (
workspace_id IN (
SELECT workspace_id FROM user_profiles
WHERE id = auth.uid()
AND role IN ('owner', 'admin')
)
);
-- Usage records accessible by workspace members
CREATE POLICY "Workspace members can view usage" ON usage_records
FOR SELECT USING (
workspace_id IN (
SELECT workspace_id FROM user_profiles
WHERE id = auth.uid()
)
);
JWT Middleware Implementation
Node.js/Express Middleware
// middleware/auth.js
const jwt = require('jsonwebtoken');
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY
);
class AuthMiddleware {
/**
* Verify JWT and extract user/license information
*/
static async verifyToken(req, res, next) {
try {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
// Verify token with Supabase
const { data: user, error } = await supabase.auth.getUser(token);
if (error || !user) {
return res.status(401).json({ error: 'Invalid token' });
}
// Enrich with license information
const enrichedUser = await this.enrichUserWithLicense(user.user);
req.user = enrichedUser;
req.token = token;
next();
} catch (error) {
console.error('Auth middleware error:', error);
return res.status(401).json({ error: 'Token verification failed' });
}
}
/**
* Extract token from Authorization header or cookies
*/
static extractToken(req) {
// Try Authorization header first
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
// Fallback to httpOnly cookie
return req.cookies?.['sasha-token'];
}
/**
* Enrich user with workspace and license data
*/
static async enrichUserWithLicense(user) {
const { data: profile } = await supabase
.from('user_profiles')
.select(`
*,
workspace:workspaces(
id,
name,
slug,
subscription:subscriptions(
id,
tier,
status,
current_period_start,
current_period_end
)
)
`)
.eq('id', user.id)
.single();
return {
...user,
profile,
workspace: profile?.workspace,
subscription: profile?.workspace?.subscription,
permissions: await this.getUserPermissions(user.id, profile?.workspace?.id)
};
}
/**
* Get user permissions based on role and subscription
*/
static async getUserPermissions(userId, workspaceId) {
if (!workspaceId) return [];
const { data: profile } = await supabase
.from('user_profiles')
.select('role')
.eq('id', userId)
.eq('workspace_id', workspaceId)
.single();
const { data: subscription } = await supabase
.from('subscriptions')
.select('tier, status')
.eq('workspace_id', workspaceId)
.single();
return this.calculatePermissions(profile?.role, subscription);
}
/**
* Calculate permissions based on role and subscription tier
*/
static calculatePermissions(role, subscription) {
const basePermissions = {
viewer: ['chat:read', 'file:read'],
member: ['chat:create', 'file:upload', 'guide:access'],
admin: ['chat:create', 'file:upload', 'guide:access', 'publish:create', 'workspace:manage'],
owner: ['*'] // All permissions
};
const tierPermissions = {
free: ['basic_models'],
starter: ['basic_models', 'file_storage_1gb'],
pro: ['advanced_models', 'file_storage_10gb', 'custom_guides'],
enterprise: ['all_models', 'unlimited_storage', 'custom_guides', 'priority_support']
};
const rolePerms = basePermissions[role] || [];
const tierPerms = subscription?.status === 'active'
? tierPermissions[subscription.tier] || []
: tierPermissions.free;
return [...rolePerms, ...tierPerms];
}
}
module.exports = AuthMiddleware;
License Validation Middleware
// middleware/license.js
class LicenseMiddleware {
/**
* Check if user has required feature access
*/
static requireFeature(featureName) {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) {
return res.status(401).json({ error: 'Authentication required' });
}
const hasAccess = await this.checkFeatureAccess(
user.workspace.id,
featureName,
user.id
);
if (!hasAccess.allowed) {
return res.status(403).json({
error: 'Feature not available in your subscription',
feature: featureName,
reason: hasAccess.reason,
upgrade_required: hasAccess.upgradeRequired
});
}
// Track usage
await this.trackUsage(user.workspace.id, user.id, featureName);
next();
} catch (error) {
console.error('License check error:', error);
return res.status(500).json({ error: 'License validation failed' });
}
};
}
/**
* Check if workspace has access to specific feature
*/
static async checkFeatureAccess(workspaceId, featureName, userId) {
// Get subscription details
const { data: workspace } = await supabase
.from('workspaces')
.select(`
subscription:subscriptions(
tier,
status,
current_period_end
)
`)
.eq('id', workspaceId)
.single();
const subscription = workspace?.subscription;
// Check subscription status
if (!subscription || subscription.status !== 'active') {
return {
allowed: false,
reason: 'Subscription inactive',
upgradeRequired: true
};
}
// Check if subscription expired
if (new Date(subscription.current_period_end) < new Date()) {
return {
allowed: false,
reason: 'Subscription expired',
upgradeRequired: true
};
}
// Get feature limits for subscription tier
const { data: limits } = await supabase
.from('subscription_limits')
.select('*')
.eq('tier', subscription.tier)
.eq('feature_name', featureName);
if (!limits || limits.length === 0) {
// Feature not defined for this tier - allow by default for higher tiers
const tierHierarchy = ['free', 'starter', 'pro', 'enterprise'];
const currentTierIndex = tierHierarchy.indexOf(subscription.tier);
const isHighTier = currentTierIndex >= 2; // pro and enterprise
return {
allowed: isHighTier,
reason: isHighTier ? 'Allowed for tier' : 'Feature not available in tier',
upgradeRequired: !isHighTier
};
}
// Check usage limits
const limit = limits[0];
const currentUsage = await this.getCurrentUsage(
workspaceId,
featureName,
limit.limit_type
);
if (currentUsage >= limit.limit_value) {
return {
allowed: false,
reason: `${limit.limit_type} limit exceeded (${currentUsage}/${limit.limit_value})`,
upgradeRequired: true
};
}
return {
allowed: true,
remaining: limit.limit_value - currentUsage
};
}
/**
* Get current usage for a feature in the specified period
*/
static async getCurrentUsage(workspaceId, featureName, limitType) {
const now = new Date();
let periodStart;
switch (limitType) {
case 'daily':
periodStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
break;
case 'monthly':
periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
break;
case 'total':
periodStart = new Date('1970-01-01');
break;
default:
periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
}
const { data, error } = await supabase
.from('usage_records')
.select('usage_count')
.eq('workspace_id', workspaceId)
.eq('feature_name', featureName)
.gte('recorded_at', periodStart.toISOString());
if (error) {
console.error('Usage query error:', error);
return 0;
}
return data?.reduce((total, record) => total + record.usage_count, 0) || 0;
}
/**
* Track feature usage
*/
static async trackUsage(workspaceId, userId, featureName, count = 1, metadata = {}) {
const now = new Date();
const periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
const periodEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
await supabase
.from('usage_records')
.insert({
workspace_id: workspaceId,
user_id: userId,
feature_name: featureName,
usage_count: count,
metadata,
recorded_at: now.toISOString(),
period_start: periodStart.toISOString(),
period_end: periodEnd.toISOString()
});
}
}
module.exports = LicenseMiddleware;
API Integration Examples
Protected Route Implementation
// routes/chat.js
const express = require('express');
const AuthMiddleware = require('../middleware/auth');
const LicenseMiddleware = require('../middleware/license');
const router = express.Router();
// Apply authentication to all chat routes
router.use(AuthMiddleware.verifyToken);
// Create new chat session
router.post('/sessions',
LicenseMiddleware.requireFeature('chat_sessions'),
async (req, res) => {
try {
const { title, model_preference } = req.body;
// Validate model access
const modelAccess = await LicenseMiddleware.checkFeatureAccess(
req.user.workspace.id,
`model_${model_preference}`,
req.user.id
);
if (!modelAccess.allowed) {
return res.status(403).json({
error: 'Model not available in your subscription',
available_models: await getAvailableModels(req.user.subscription.tier)
});
}
// Create session
const session = await createChatSession({
workspace_id: req.user.workspace.id,
user_id: req.user.id,
title,
model_preference
});
res.json({ session });
} catch (error) {
console.error('Create session error:', error);
res.status(500).json({ error: 'Failed to create session' });
}
}
);
// Send message in chat
router.post('/sessions/:sessionId/messages',
LicenseMiddleware.requireFeature('ai_queries'),
async (req, res) => {
try {
const { sessionId } = req.params;
const { content, model } = req.body;
// Verify session ownership
const session = await getChatSession(sessionId, req.user.workspace.id);
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Process message with AI
const response = await processAIMessage({
content,
model,
user: req.user,
session
});
res.json({ message: response });
} catch (error) {
console.error('Send message error:', error);
res.status(500).json({ error: 'Failed to process message' });
}
}
);
module.exports = router;
Frontend Token Management
// utils/auth.js (Frontend)
class AuthManager {
constructor() {
this.supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
this.setupTokenRefresh();
}
/**
* Login with email/password
*/
async login(email, password) {
try {
const { data, error } = await this.supabase.auth.signInWithPassword({
email,
password
});
if (error) throw error;
// Store tokens securely
this.storeTokens(data.session);
return { user: data.user, session: data.session };
} catch (error) {
console.error('Login error:', error);
throw new Error(error.message || 'Login failed');
}
}
/**
* Logout user
*/
async logout() {
try {
const { error } = await this.supabase.auth.signOut();
if (error) throw error;
this.clearTokens();
window.location.href = '/login';
} catch (error) {
console.error('Logout error:', error);
}
}
/**
* Get current session with license data
*/
async getSession() {
try {
const { data: { session }, error } = await this.supabase.auth.getSession();
if (error) throw error;
if (!session) return null;
// Enrich with license information
const enrichedSession = await this.enrichSessionWithLicense(session);
return enrichedSession;
} catch (error) {
console.error('Get session error:', error);
return null;
}
}
/**
* Setup automatic token refresh
*/
setupTokenRefresh() {
this.supabase.auth.onAuthStateChange(async (event, session) => {
if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
this.storeTokens(session);
} else if (event === 'SIGNED_OUT') {
this.clearTokens();
}
});
}
/**
* Store tokens securely (httpOnly cookies in production)
*/
storeTokens(session) {
if (!session) return;
// In development, use localStorage
if (process.env.NODE_ENV === 'development') {
localStorage.setItem('sasha-token', session.access_token);
localStorage.setItem('sasha-refresh', session.refresh_token);
} else {
// In production, tokens should be stored in httpOnly cookies
// This would be handled by your backend API
document.cookie = `sasha-token=${session.access_token}; HttpOnly; Secure; SameSite=Strict; Max-Age=900`; // 15 min
document.cookie = `sasha-refresh=${session.refresh_token}; HttpOnly; Secure; SameSite=Strict; Max-Age=604800`; // 7 days
}
}
/**
* Clear stored tokens
*/
clearTokens() {
if (process.env.NODE_ENV === 'development') {
localStorage.removeItem('sasha-token');
localStorage.removeItem('sasha-refresh');
} else {
document.cookie = 'sasha-token=; Max-Age=0';
document.cookie = 'sasha-refresh=; Max-Age=0';
}
}
/**
* Enrich session with workspace and license data
*/
async enrichSessionWithLicense(session) {
try {
const { data: profile, error } = await this.supabase
.from('user_profiles')
.select(`
*,
workspace:workspaces(
id,
name,
slug,
subscription:subscriptions(
id,
tier,
status,
current_period_start,
current_period_end
)
)
`)
.eq('id', session.user.id)
.single();
if (error) throw error;
return {
...session,
user: {
...session.user,
profile,
workspace: profile?.workspace,
subscription: profile?.workspace?.subscription
}
};
} catch (error) {
console.error('Enrich session error:', error);
return session;
}
}
/**
* Make authenticated API request
*/
async apiRequest(url, options = {}) {
const session = await this.getSession();
if (!session) {
throw new Error('Not authenticated');
}
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`,
...options.headers
};
const response = await fetch(url, {
...options,
headers
});
if (response.status === 401) {
// Token expired, try to refresh
await this.supabase.auth.refreshSession();
const newSession = await this.getSession();
if (newSession) {
// Retry with new token
headers.Authorization = `Bearer ${newSession.access_token}`;
return fetch(url, { ...options, headers });
} else {
// Refresh failed, redirect to login
this.logout();
throw new Error('Session expired');
}
}
return response;
}
}
export default new AuthManager();
Configuration & Environment
Environment Variables
# Supabase Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# JWT Configuration
JWT_SECRET_KEY=your-jwt-secret
JWT_ACCESS_TOKEN_EXPIRES_IN=15m
JWT_REFRESH_TOKEN_EXPIRES_IN=7d
# Cookie Configuration
COOKIE_SECRET=your-cookie-secret
COOKIE_DOMAIN=your-domain.com
COOKIE_SECURE=true
# Licensing Configuration
STRIPE_SECRET_KEY=sk_test_your-stripe-key
STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret
# Feature Flags
ENABLE_SOCIAL_LOGIN=true
ENABLE_MFA=false
ENABLE_USAGE_TRACKING=true
Supabase Configuration
-- Insert default subscription limits
INSERT INTO subscription_limits (tier, feature_name, limit_value, limit_type) VALUES
-- Free tier limits
('free', 'ai_queries', 100, 'monthly'),
('free', 'file_storage_mb', 100, 'total'),
('free', 'team_members', 1, 'total'),
('free', 'workspaces', 1, 'total'),
-- Starter tier limits
('starter', 'ai_queries', 1000, 'monthly'),
('starter', 'file_storage_mb', 1000, 'total'),
('starter', 'team_members', 5, 'total'),
('starter', 'workspaces', 3, 'total'),
-- Pro tier limits
('pro', 'ai_queries', 10000, 'monthly'),
('pro', 'file_storage_mb', 10000, 'total'),
('pro', 'team_members', 25, 'total'),
('pro', 'workspaces', 10, 'total'),
-- Enterprise tier (unlimited)
('enterprise', 'ai_queries', 999999, 'monthly'),
('enterprise', 'file_storage_mb', 999999, 'total'),
('enterprise', 'team_members', 999999, 'total'),
('enterprise', 'workspaces', 999999, 'total');
-- Enable Row Level Security
ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
ALTER TABLE usage_records ENABLE ROW LEVEL SECURITY;
Testing & Validation
Authentication Test Suite
// tests/auth.test.js
const request = require('supertest');
const app = require('../app');
describe('Authentication Flow', () => {
let authToken;
let refreshToken;
beforeEach(async () => {
// Setup test user and workspace
await setupTestData();
});
afterEach(async () => {
// Cleanup test data
await cleanupTestData();
});
describe('Login Flow', () => {
test('should login with valid credentials', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: 'test@example.com',
password: 'testpassword'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('access_token');
expect(response.body).toHaveProperty('refresh_token');
expect(response.body).toHaveProperty('user');
authToken = response.body.access_token;
refreshToken = response.body.refresh_token;
});
test('should reject invalid credentials', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: 'test@example.com',
password: 'wrongpassword'
});
expect(response.status).toBe(401);
expect(response.body).toHaveProperty('error');
});
});
describe('Token Validation', () => {
test('should access protected route with valid token', async () => {
const response = await request(app)
.get('/api/chat/sessions')
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
});
test('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/chat/sessions')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(401);
});
test('should refresh expired token', async () => {
const response = await request(app)
.post('/auth/refresh')
.send({
refresh_token: refreshToken
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('access_token');
});
});
describe('License Validation', () => {
test('should allow feature access for valid subscription', async () => {
const response = await request(app)
.post('/api/chat/sessions')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Test Session',
model_preference: 'claude-3'
});
expect(response.status).toBe(200);
});
test('should deny feature access for insufficient license', async () => {
// Create user with free tier
const freeUserToken = await createFreeUserToken();
const response = await request(app)
.post('/api/chat/sessions')
.set('Authorization', `Bearer ${freeUserToken}`)
.send({
title: 'Test Session',
model_preference: 'gpt-4' // Not available in free tier
});
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('upgrade_required', true);
});
test('should track usage correctly', async () => {
// Make multiple requests
for (let i = 0; i < 3; i++) {
await request(app)
.post('/api/chat/sessions/:sessionId/messages')
.set('Authorization', `Bearer ${authToken}`)
.send({
content: `Test message ${i}`,
model: 'claude-3'
});
}
// Check usage tracking
const usageResponse = await request(app)
.get('/api/usage/current')
.set('Authorization', `Bearer ${authToken}`);
expect(usageResponse.status).toBe(200);
expect(usageResponse.body.ai_queries).toBe(3);
});
});
});
Error Handling & Edge Cases
Authentication Error Scenarios
// utils/authErrors.js
class AuthError extends Error {
constructor(message, code, statusCode = 401) {
super(message);
this.name = 'AuthError';
this.code = code;
this.statusCode = statusCode;
}
}
class LicenseError extends Error {
constructor(message, feature, upgradeRequired = true) {
super(message);
this.name = 'LicenseError';
this.feature = feature;
this.upgradeRequired = upgradeRequired;
this.statusCode = 403;
}
}
// Error handler middleware
const errorHandler = (error, req, res, next) => {
if (error instanceof AuthError) {
return res.status(error.statusCode).json({
error: error.message,
code: error.code,
type: 'authentication_error'
});
}
if (error instanceof LicenseError) {
return res.status(error.statusCode).json({
error: error.message,
feature: error.feature,
upgrade_required: error.upgradeRequired,
type: 'license_error'
});
}
// Generic error
console.error('Unhandled error:', error);
return res.status(500).json({
error: 'Internal server error',
type: 'server_error'
});
};
module.exports = { AuthError, LicenseError, errorHandler };
Token Refresh Edge Cases
// utils/tokenRefresh.js
class TokenRefreshManager {
constructor() {
this.refreshPromises = new Map();
}
/**
* Handle concurrent refresh requests
*/
async refreshToken(refreshToken) {
// Prevent multiple simultaneous refresh requests
if (this.refreshPromises.has(refreshToken)) {
return this.refreshPromises.get(refreshToken);
}
const refreshPromise = this.performRefresh(refreshToken);
this.refreshPromises.set(refreshToken, refreshPromise);
try {
const result = await refreshPromise;
return result;
} finally {
this.refreshPromises.delete(refreshToken);
}
}
async performRefresh(refreshToken) {
try {
const { data, error } = await supabase.auth.refreshSession({
refresh_token: refreshToken
});
if (error) {
throw new AuthError('Token refresh failed', 'REFRESH_FAILED');
}
// Verify subscription is still active
const enrichedSession = await this.verifySubscriptionStatus(data.session);
return enrichedSession;
} catch (error) {
if (error instanceof AuthError) {
throw error;
}
throw new AuthError('Token refresh error', 'REFRESH_ERROR');
}
}
async verifySubscriptionStatus(session) {
const { data: workspace } = await supabase
.from('workspaces')
.select(`
subscription:subscriptions(
status,
current_period_end
)
`)
.eq('owner_id', session.user.id)
.single();
const subscription = workspace?.subscription;
if (!subscription || subscription.status !== 'active') {
throw new LicenseError('Subscription inactive', 'subscription_inactive');
}
if (new Date(subscription.current_period_end) < new Date()) {
throw new LicenseError('Subscription expired', 'subscription_expired');
}
return session;
}
}
module.exports = new TokenRefreshManager();
Monitoring & Analytics
Authentication Metrics
// utils/authMetrics.js
class AuthMetrics {
static async trackLogin(userId, workspaceId, metadata = {}) {
await supabase
.from('auth_events')
.insert({
user_id: userId,
workspace_id: workspaceId,
event_type: 'login',
metadata: {
ip_address: metadata.ip,
user_agent: metadata.userAgent,
timestamp: new Date().toISOString()
}
});
}
static async trackTokenRefresh(userId, workspaceId) {
await supabase
.from('auth_events')
.insert({
user_id: userId,
workspace_id: workspaceId,
event_type: 'token_refresh',
metadata: {
timestamp: new Date().toISOString()
}
});
}
static async trackLicenseViolation(userId, workspaceId, feature, reason) {
await supabase
.from('auth_events')
.insert({
user_id: userId,
workspace_id: workspaceId,
event_type: 'license_violation',
metadata: {
feature,
reason,
timestamp: new Date().toISOString()
}
});
}
static async getAuthStats(workspaceId, days = 30) {
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const { data } = await supabase
.from('auth_events')
.select('event_type, created_at')
.eq('workspace_id', workspaceId)
.gte('created_at', startDate.toISOString());
return {
totalLogins: data?.filter(e => e.event_type === 'login').length || 0,
tokenRefreshes: data?.filter(e => e.event_type === 'token_refresh').length || 0,
licenseViolations: data?.filter(e => e.event_type === 'license_violation').length || 0,
dailyStats: this.groupByDay(data)
};
}
static groupByDay(events) {
const stats = {};
events?.forEach(event => {
const date = event.created_at.split('T')[0];
if (!stats[date]) {
stats[date] = { logins: 0, refreshes: 0, violations: 0 };
}
stats[date][event.event_type === 'login' ? 'logins' :
event.event_type === 'token_refresh' ? 'refreshes' : 'violations']++;
});
return stats;
}
}
module.exports = AuthMetrics;
Security Best Practices
Token Security Checklist
Secure Token Storage
- Use httpOnly cookies for production
- Implement CSRF protection
- Never store tokens in localStorage in production
Token Rotation
- Rotate tokens on suspicious activity
- Implement token blacklisting
- Use short-lived access tokens (15 minutes)
License Validation
- Verify subscription status on every request
- Implement real-time license updates
- Track and alert on license violations
Rate Limiting
- Implement auth endpoint rate limiting
- Add progressive delays for failed attempts
- Monitor for brute force attacks
Audit Logging
- Log all authentication events
- Track license violations
- Monitor for anomalous patterns
Next Steps
Implementation Roadmap
Phase 1: Basic Authentication (Week 1)
- Set up Supabase Auth
- Implement JWT middleware
- Create user registration/login
Phase 2: Licensing Integration (Week 2)
- Database schema implementation
- License validation middleware
- Usage tracking system
Phase 3: Advanced Features (Week 3)
- Multi-workspace support
- Role-based access control
- Subscription management
Phase 4: Security & Monitoring (Week 4)
- Comprehensive audit logging
- Security event monitoring
- Performance optimization
Future Enhancements
- Multi-Factor Authentication (MFA)
- Single Sign-On (SSO) integration
- Advanced usage analytics
- Automated license compliance
- Real-time subscription updates
This comprehensive guide provides the foundation for implementing secure, scalable JWT authentication with Supabase Auth and sophisticated licensing capabilities in Sasha Studio.