Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| components | ||
| contexts | ||
| lib | ||
| services | ||
| types | ||
| utils | ||
| auth.tsx | ||
| index.ts | ||
| README-GOOGLE-AUTH.md | ||
| README.md | ||
Memoro Authentication Integration
This document provides an overview of the authentication integration implemented in the Memoro app. It covers how the app integrates with the middleware authentication system and Supabase.
Table of Contents
- Overview
- Authentication Flow
- File Structure
- Key Components
- Platform-Specific Implementations
- Token Management
- Middleware API Endpoints
- Row Level Security (RLS)
- Security Considerations
- Troubleshooting
Overview
The Memoro app uses a middleware authentication system as a bridge between the frontend and Supabase, providing a secure and centralized authentication solution. The middleware issues compatible JWT tokens that work with Supabase's Row Level Security (RLS) policies.
┌─────────────┐ ┌────────────────┐ ┌─────────────┐
│ Memoro │────▶│ Middleware │────▶│ Supabase │
│ App │◀────│ Authentication │◀────│ Backend │
└─────────────┘ └────────────────┘ └─────────────┘
Key Features
- Application Tokens: The middleware issues app-specific JWT tokens (appTokens) that are compatible with Supabase's auth system
- Token Refresh: Implements secure token refresh mechanism to maintain session persistence
- Role-Based Access: Supports role-based access control in the JWT claims
- Secure Storage: Uses secure storage for all authentication tokens
- Email Persistence: Maintains email data even when tokens don't contain it
- Cross-Platform: Supports both web and native mobile platforms
Authentication Flow
The standard authentication flow works as follows:
- User enters credentials in the app (login.tsx or register.tsx)
- The app sends credentials to the middleware with the appId
- Middleware validates credentials and returns three tokens:
manaToken: Core authentication token for the middlewareappToken: Supabase-compatible JWT with appropriate claimsrefreshToken: Used to get new tokens when the appToken expires
- The app stores these tokens securely using the safeStorage utility (platform-specific)
- User information is extracted from the token and stored in the AuthContext
- The Supabase client is configured to use the appToken for all requests
- All subsequent database operations are subject to RLS policies based on the JWT claims
File Structure
features/
auth/
components/
AuthErrorDisplay.tsx - Component for displaying authentication errors
index.ts - Export file for components
contexts/
AuthContext.tsx - React Context for global auth state management
index.ts - Export file for contexts
index.ts - Main export file for auth feature
lib/
index.ts - Export file for libraries
supabaseClient.ts - Supabase client configuration with JWT integration
services/
authService.ts - Core authentication service with API integrations
index.ts - Export file for services
utils/
index.ts - Export file for utilities
safeStorage.ts - Secure storage utility for tokens (React Native)
safeStorage.web.ts - Web-specific storage utility using localStorage
README.md - This documentation file
Key Components
AuthContext
The AuthContext provides a global authentication state and methods for authentication:
// Key properties and methods
{
isAuthenticated: boolean; // Whether the user is authenticated
user: User | null; // Current user information
loading: boolean; // Loading state for async operations
signIn: (email, password) => Promise<Result>; // Sign in with email/password
signUp: (email, password) => Promise<Result>; // Register a new user
signOut: () => Promise<void>; // Sign out the current user
}
authService
The authService provides core authentication functionality:
// Key methods
{
signIn: (email, password) => Promise<Result>; // Sign in with credentials
signUp: (email, password) => Promise<Result>; // Register a new user
signOut: () => Promise<void>; // Sign out
refreshTokens: (refreshToken) => Promise<Tokens>; // Refresh expired tokens
validateToken: () => Promise<boolean>; // Validate current token
getUserFromToken: () => Promise<UserData>; // Extract user data from token
isAuthenticated: () => Promise<boolean>; // Check authentication status
clearAuthStorage: () => Promise<void>; // Clear all auth data
}
Supabase Integration
The supabaseClient.ts file configures Supabase to work with our authentication system:
- Creates a Supabase client with custom auth handling
- Adds JWT to all API requests
- Handles token refresh and session persistence
- Configures realtime subscriptions with authentication
Platform-Specific Implementations
The authentication system uses platform-specific implementations for certain components to support both web and native mobile platforms.
Web Platform
For web platforms, the system uses:
safeStorage.web.ts: Storage implementation using browser's localStorage- Synchronous token retrieval
Example web storage implementation:
// Web storage implementation (safeStorage.web.ts)
export const safeStorage = {
setItem: async <T>(key: string, value: T): Promise<void> => {
try {
const jsonValue = JSON.stringify(value);
localStorage.setItem(key, jsonValue);
} catch (e) {
console.error('Error saving data', e);
}
},
getItem: async <T>(key: string): Promise<T | null> => {
try {
const jsonValue = localStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.error('Error reading data', e);
return null;
}
},
// ... other methods
};
React Native Platform
For native mobile platforms, the system uses:
safeStorage.ts: Storage implementation using React Native's AsyncStorage- Asynchronous token retrieval and storage
Example native storage implementation:
// React Native storage implementation (safeStorage.ts)
export const safeStorage = {
setItem: async <T>(key: string, value: T): Promise<void> => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (e) {
console.error('Error saving data', e);
}
},
getItem: async <T>(key: string): Promise<T | null> => {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.error('Error reading data', e);
return null;
}
},
// ... other methods
};
Token Management
Storage
All tokens are stored securely using the safeStorage utility:
// Keys used for token storage
const STORAGE_KEYS = {
MANA_TOKEN: '@auth/manaToken',
APP_TOKEN: '@auth/appToken',
REFRESH_TOKEN: '@auth/refreshToken',
USER_EMAIL: '@auth/userEmail', // Email is stored separately to handle tokens without email
};
Token Refresh
Token refresh happens automatically when:
- The current token is validated and found to be expired
- The
authService.validateToken()method is called - The
authService.isAuthenticated()method is called
When a token is refreshed:
- New tokens are obtained from the middleware
- Tokens are stored securely
- User data is extracted and the AuthContext is updated
- Supabase client is reconfigured with the new token
Email Persistence
To handle cases where the refreshed token does not contain the email field:
- The user's email is stored separately in secure storage during login/registration
- When decoding a token that doesn't have the email field, the email is retrieved from storage
- This ensures user information remains complete even after token refresh
Middleware API Endpoints
The middleware service provides the following endpoints for authentication:
| Endpoint | Method | Description | Parameters |
|---|---|---|---|
/auth/signin |
POST | Authenticate with email/password | email, password, appId |
/auth/google-signin |
POST | Authenticate with Google | token (Google ID token), appId |
/auth/refresh |
POST | Refresh an expired token | refreshToken, appId |
/auth/validate |
POST | Validate an existing token | appToken, appId |
/auth/logout |
POST | Log out and revoke refresh token | refreshToken |
Row Level Security (RLS)
Supabase uses Row Level Security (RLS) policies to restrict access to data. The middleware-issued JWTs contain claims that RLS policies can use to determine access rights.
Example RLS Policies
-- Enable RLS on a table
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
-- Basic policy: Users can only access their own data
CREATE POLICY "Users can only access their own data"
ON your_table
FOR ALL
TO authenticated
USING ((current_setting('request.jwt.claims', true)::json->>'sub')::text = user_id);
-- Role-based policy: Admins can access all data
CREATE POLICY "Admins can access all data"
ON your_table
FOR ALL
TO authenticated
USING ((current_setting('request.jwt.claims', true)::json->>'role')::text = 'admin');
Key JWT Claims Used in RLS
sub: The subject claim containing the user IDrole: The user's role for role-based access controlapp_id: The application ID that the token was issued for- Custom claims: Additional claims can be added to the token for specific application needs
Security Considerations
-
Token Storage:
- Uses the safeStorage utility which is platform-aware
- Web platform: Uses localStorage
- Mobile platforms: Uses AsyncStorage
- Both implementations are wrapped with error handling and consistent API
-
Token Expiration:
- Tokens expire after 1 hour by default
- Automatic token refresh is implemented
- Handles graceful redirection to login on refresh failures
-
Automatic Cleanup:
- Auth storage is automatically cleared on:
- Manual logout
- Token refresh failures
- Authentication errors
- Auth storage is automatically cleared on:
-
Loading States:
- Login/register buttons are disabled during authentication
- Loading indicators prevent multiple simultaneous authentication attempts
-
HTTPS:
- Always use HTTPS for all API communications
- Never transmit tokens over insecure connections
Troubleshooting
Common Issues
-
"Lost user email/ID on settings page"
- This can happen when tokens are refreshed and the new token doesn't contain the email field
- Solution: The app now stores email separately and retrieves it when needed
-
"Multiple authentication attempts"
- Previously possible to trigger multiple sign-in attempts
- Solution: Login/register buttons are now disabled during authentication
-
"Authentication state inconsistency"
- Can occur if token refresh fails but app state isn't updated
- Solution: Auth storage is now cleared on refresh failures with redirection to login
-
"Invalid JWT" errors with Supabase
- Ensure the middleware is generating tokens with the correct format
- Verify the JWT secret used by the middleware matches your Supabase project
-
"Authentication works but data access fails"
- Check that RLS policies are configured correctly
- Ensure the JWT claims match what your RLS policies expect (especially user ID)
-
"Web platform errors"
- Check that the web implementation is being loaded correctly
- Verify that the
windowobject is defined before accessing localStorage - Ensure metro.config.js is properly configured to prioritize .web.ts files
Debugging Tips
- Check browser console or app logs for authentication errors
- Use the AuthErrorDisplay component to show authentication errors
- Verify network requests to the middleware service
- Check that tokens are properly stored in secure storage
- Verify that the loading state is properly managed during auth operations
- Use JWT.io to decode and inspect your tokens during development
- For web-specific issues, check browser console and network tab for errors
- For native-specific issues, check the Expo/React Native logs
References
- Supabase JWT Integration Blueprint - Original integration blueprint
- Audio Recording Documentation - Documentation for audio recording features
- Supabase Auth Documentation - Official Supabase auth documentation
- JWT.io - For decoding and inspecting JWT tokens