User Management¶
FormFiller provides built-in user management features: registration, login, profile management.
Authentication Modes¶
JWT Token Based¶
The default authentication mode uses JWT (JSON Web Token):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Login │────▶│ Backend │────▶│ JWT Token │
│ Form │ │ /auth/login│ │ generation │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌──────────────────────────┘
▼
┌─────────────┐
│ localStorage│ Token storage
│ token │
└──────┬──────┘
│
▼
┌─────────────┐
│ Every API │ Authorization: Bearer <token>
│ call │
└─────────────┘
Google OAuth¶
OAuth 2.0 login with Google account:
// Login button
<Button onClick={() => window.location.href = `${API_URL}/auth/google`}>
Login with Google
</Button>
AuthContext¶
User state management with React Context:
import { useAuth } from '../contexts/AuthContext';
function MyComponent() {
const {
user, // Logged in user
isAuthenticated,// Boolean - is logged in
isLoading, // Boolean - loading
login, // Login function
logout, // Logout function
updateProfile // Profile update
} = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <div>Welcome, {user.name}!</div>;
}
AuthProvider¶
At application root:
// App.tsx
import { AuthProvider } from './contexts/AuthContext';
function App() {
return (
<AuthProvider>
<Router>
<Routes>
{/* ... */}
</Routes>
</Router>
</AuthProvider>
);
}
Login Process¶
Login Page¶
// pages/login/LoginPage.tsx
function LoginPage() {
const { login } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await login(email, password);
// Success - AuthContext redirects
} catch (err) {
setError('Invalid email or password');
}
};
return (
<form onSubmit={handleSubmit}>
<TextBox value={email} onValueChanged={e => setEmail(e.value)} />
<TextBox mode="password" value={password} onValueChanged={e => setPassword(e.value)} />
<Button type="submit" text="Login" />
{error && <div className="error">{error}</div>}
</form>
);
}
Protected Routes¶
// components/ProtectedRoute.tsx
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
const location = useLocation();
if (isLoading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
// Usage
<Route
path="/form/:configId"
element={
<ProtectedRoute>
<FormPage />
</ProtectedRoute>
}
/>
Profile Management¶
Profile Page¶
function ProfilePage() {
const { user, updateProfile } = useAuth();
const [formData, setFormData] = useState({
name: user?.name || '',
email: user?.email || ''
});
const handleSave = async () => {
await updateProfile(formData);
notify('Profile updated', 'success');
};
return (
<Form formData={formData} onFieldDataChanged={handleFieldChange}>
<SimpleItem dataField="name" label={{ text: 'Name' }} />
<SimpleItem dataField="email" label={{ text: 'Email' }} />
<ButtonItem>
<ButtonOptions text="Save" onClick={handleSave} />
</ButtonItem>
</Form>
);
}
Password Change¶
const handlePasswordChange = async () => {
await authService.changePassword({
currentPassword,
newPassword,
confirmPassword
});
// Logout - need to login again
logout();
notify('Password changed. Please login again.', 'info');
};
Token Management¶
Token Storage¶
// services/authService.ts
const TOKEN_KEY = 'formfiller_token';
export const authService = {
setToken(token: string) {
localStorage.setItem(TOKEN_KEY, token);
},
getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
},
removeToken() {
localStorage.removeItem(TOKEN_KEY);
}
};
API Calls with Authentication¶
// api/client.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: API_URL
});
// Request interceptor - add token
apiClient.interceptors.request.use(config => {
const token = authService.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor - handle 401
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
authService.removeToken();
window.location.href = '/login';
}
return Promise.reject(error);
}
);
User Data¶
User Object¶
interface User {
id: string;
name: string;
email: string;
avatar?: string;
roles: string[]; // Roles
permissions: string[]; // Permissions
sites: Site[]; // Available sites (multisite)
currentSite?: Site; // Current site
preferences: {
language: string;
theme: 'light' | 'dark';
notifications: boolean;
};
}
Security Considerations¶
Token Security¶
- Token only over HTTPS
- HttpOnly cookie usage (optional, more secure)
- Token expiration (default: 24 hours)
- Refresh token rotation
Protection¶
- CSRF protection
- XSS protection (React automatic escape)
- Rate limiting on backend
- Password strength validation