| name | forsion-login-integration |
| description | Integrate frontend applications with the Forsion unified backend login system. Use when integrating authentication, setting up login flow, connecting to Forsion backend APIs, or implementing user authentication for any frontend project (React, Vue, Angular, etc.). |
Forsion Login Integration
This skill guides you through integrating frontend applications with the Forsion unified backend authentication system.
When to Use This Skill
Use this skill when:
- Integrating a new frontend project with Forsion backend authentication
- Setting up login, registration, or password reset flows
- Implementing JWT token management
- Configuring API clients with authentication headers
- Troubleshooting authentication issues
Important: JWT Token vs JWT Secret
Frontend applications ONLY need JWT tokens, NOT the JWT_SECRET:
| Concept | Who Uses | What Is It |
|---|
| JWT Token | Frontend / All API clients | A string returned by /api/auth/login that must be sent in the Authorization: Bearer <token> header for all authenticated API calls |
| JWT Secret (JWT_SECRET) | Backend server only | A secret key stored in backend .env used to sign and verify tokens. NEVER share with or configure in frontend |
Frontend Integration Summary:
- Call login API → Receive
token string in response
- Save token to localStorage
- Send token in
Authorization: Bearer <token> header for ALL authenticated API requests
- Frontend does NOT need, and should NOT have access to, JWT_SECRET
Which APIs need the token?
- Login/Register APIs: No token needed (you call these to GET a token)
- All other APIs: Token required (user info, credits, payments, storage, AI chat, etc.)
If you are asked to configure "JWT_SECRET" in frontend, that is incorrect. Frontend only uses the token string.
Quick Start Integration (3 Steps)
Step 1: Create Auth Utility
Create src/utils/authRedirect.ts (or .js for JavaScript projects):
const AUTH_BASE_URL = import.meta.env.VITE_AUTH_URL || 'http://localhost:3001/auth';
const TOKEN_KEY = 'auth_token';
export function initAuth(): boolean {
const urlParams = new URLSearchParams(window.location.search);
const tokenFromUrl = urlParams.get('token');
if (tokenFromUrl) {
localStorage.setItem(TOKEN_KEY, tokenFromUrl);
urlParams.delete('token');
const newUrl = urlParams.toString()
? `${window.location.pathname}?${urlParams}`
: window.location.pathname;
window.history.replaceState({}, '', newUrl);
return true;
}
const storedToken = localStorage.getItem(TOKEN_KEY);
return !!storedToken;
}
export function redirectToLogin(appName?: string): void {
const currentUrl = encodeURIComponent(window.location.href);
let authUrl = `${AUTH_BASE_URL}?redirect=${currentUrl}`;
if (appName) {
authUrl += `&app=${appName}`;
}
window.location.href = authUrl;
}
export async function validateAndRedirect(apiBaseUrl: string, appName?: string): Promise<boolean> {
const token = localStorage.getItem(TOKEN_KEY);
if (!token) {
redirectToLogin(appName);
return false;
}
try {
const response = await fetch(`${apiBaseUrl}/api/auth/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
return true;
}
localStorage.removeItem(TOKEN_KEY);
redirectToLogin(appName);
return false;
} catch (error) {
console.error('Token validation failed:', error);
return true;
}
}
export function logout(appName?: string): void {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem('current_username');
redirectToLogin(appName);
}
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
Step 2: Initialize Authentication in App Entry
For React (App.tsx/main.tsx):
import { useEffect, useState } from 'react';
import { initAuth, validateAndRedirect } from './utils/authRedirect';
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const checkAuth = async () => {
initAuth();
const isValid = await validateAndRedirect(
import.meta.env.VITE_API_URL,
'your-app-name'
);
setIsAuthenticated(isValid);
setIsLoading(false);
};
checkAuth();
}, []);
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return null;
return <YourApp />;
}
For Vue (App.vue):
<template>
<div v-if="isLoading">Loading...</div>
<YourApp v-else-if="isAuthenticated" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { initAuth, validateAndRedirect } from './utils/authRedirect';
const isAuthenticated = ref(false);
const isLoading = ref(true);
onMounted(async () => {
initAuth();
const isValid = await validateAndRedirect(
import.meta.env.VITE_API_URL,
'your-app-name'
);
isAuthenticated.value = isValid;
isLoading.value = false;
});
</script>
Step 3: Configure Environment Variables
Create .env file:
# Development
VITE_API_URL=http://localhost:3001
VITE_AUTH_URL=http://localhost:3001/auth
# Production (update with actual URLs)
# VITE_API_URL=https://api.forsion.com
# VITE_AUTH_URL=https://api.forsion.com/auth
API Integration
IMPORTANT: All API calls (except login/register) require the JWT token in the Authorization header. The frontend uses the token string (not JWT_SECRET) returned from login.
Setting Up API Client with Axios
import axios from 'axios';
import { getToken, logout } from './utils/authRedirect';
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
apiClient.interceptors.request.use((config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
logout('your-app-name');
}
return Promise.reject(error);
}
);
export default apiClient;
Setting Up API Client with Fetch
import { getToken, logout } from './utils/authRedirect';
export async function apiFetch(endpoint: string, options: RequestInit = {}) {
const token = getToken();
const headers = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
try {
const response = await fetch(
`${import.meta.env.VITE_API_URL}${endpoint}`,
{ ...options, headers }
);
if (response.status === 401) {
logout('your-app-name');
throw new Error('Unauthorized');
}
return response;
} catch (error) {
throw error;
}
}
Key API Endpoints
Authentication Endpoints
| Endpoint | Method | Purpose | Auth Required |
|---|
/api/auth/login | POST | Username/email/phone + password login | No |
/api/auth/login-sms | POST | Phone + verification code login | No |
/api/auth/register | POST | Register new user (requires invite code) | No |
/api/auth/send-code | POST | Send SMS verification code | No |
/api/auth/reset-password | POST | Reset password via SMS code | No |
/api/auth/me | GET | Get current user info | Yes |
/api/auth/password | PUT | Change password | Yes |
Login Request Examples
Password Login:
const response = await fetch(`${API_URL}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: 'user@example.com',
password: 'password123'
})
});
const { token, user } = await response.json();
SMS Verification Code Login:
await fetch(`${API_URL}/api/auth/send-code`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
target: '13800138000',
type: 'phone',
purpose: 'login'
})
});
const response = await fetch(`${API_URL}/api/auth/login-sms`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: '13800138000',
code: '123456'
})
});
const { token, user } = await response.json();
Registration:
const response = await fetch(`${API_URL}/api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'newuser@example.com',
password: 'password123',
invite_code: 'INVITE123'
})
});
const { token, user } = await response.json();
Authentication Flow
┌─────────────────────────────────────────────────────────┐
│ User visits your frontend app │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ Check local token │
│ (localStorage) │
└─────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Token exists │ │ Token missing │
└───────────────┘ └─────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────────────────┐
│ Validate │ │ Redirect to Forsion login │
│ GET /auth/me │ │ /auth?redirect=current_url │
└───────────────┘ └─────────────────────────────┘
│ │
┌────┴────┐ ▼
│ │ ┌─────────────────┐
▼ ▼ │ User logs in │
┌──────┐ ┌──────┐ └─────────────────┘
│Valid │ │Invalid │
└──────┘ └──────┘ ▼
│ │ ┌─────────────────────────────┐
▼ ▼ │ Redirect back with token │
┌──────┐ ┌───────────┐ │ ?token=xxx │
│Enter │ │Clear token│ └─────────────────────────────┘
│ App │ │Redirect │ │
└──────┘ └───────────┘ ▼
┌─────────────────────────────┐
│ App saves token │
│ Clears URL param │
└─────────────────────────────┘
Common Issues & Solutions
Issue: 401 Unauthorized on API Calls
Symptoms: API returns 401 even with token present
Solutions:
- Check token is being sent in header:
Authorization: Bearer <token> (use the token string from login response, NOT JWT_SECRET)
- Verify token hasn't expired (default: 7 days)
- Check token format (no extra spaces or quotes)
- Verify API base URL is correct
- Ensure you're using the token (e.g.,
eyJhbGciOiJIUzI1NiIs...), not the JWT_SECRET from backend
Issue: Token Not Saved After Login
Symptoms: User redirected back but not authenticated
Solutions:
- Verify
initAuth() is called on app mount
- Check URL parameter name is
token (lowercase)
- Check localStorage is accessible (not blocked by browser)
- Verify
auth_token key in localStorage
Issue: CORS Errors
Symptoms: Network requests fail with CORS errors
Solutions:
- Backend must include your frontend origin in CORS allowlist
- Contact backend admin to add your domain
- Development: Check
http://localhost:<port> is allowed
- Production: Ensure your domain is in backend's
ALLOWED_ORIGINS
Issue: Infinite Redirect Loop
Symptoms: App keeps redirecting between login and main app
Solutions:
- Ensure
validateAndRedirect only runs once on mount
- Check token validation endpoint
/api/auth/me is accessible
- Verify network errors don't trigger unnecessary redirects
- Add loading state to prevent re-execution
Issue: SMS Verification Code Not Received
Symptoms: Code sending succeeds but SMS not received
Solutions:
- Check phone number format (China: 11 digits, starts with 1)
- Wait 60 seconds between requests (rate limiting)
- Check spam/blocked messages on phone
- Development: Check server logs for generated code
- Verify backend SMS service (Aliyun) is configured
Advanced Configurations
Multi-Tab Synchronization
Sync login state across browser tabs:
window.addEventListener('storage', (e) => {
if (e.key === 'auth_token') {
if (!e.newValue) {
window.location.href = '/login';
} else {
validateAndRedirect(import.meta.env.VITE_API_URL, 'your-app');
}
}
});
Custom Logout Handler
export async function logoutWithCleanup(appName?: string) {
try {
await fetch(`${API_URL}/api/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
} catch (error) {
console.error('Logout request failed:', error);
}
localStorage.removeItem('auth_token');
localStorage.removeItem('current_username');
localStorage.removeItem('user_info');
sessionStorage.clear();
redirectToLogin(appName);
}
Protected Route Component (React)
import { Navigate } from 'react-router-dom';
import { getToken } from './utils/authRedirect';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const token = getToken();
if (!token) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
}
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
Protected Route (Vue Router)
import { getToken } from './utils/authRedirect';
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const token = getToken();
if (requiresAuth && !token) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
next();
}
});
App Identifiers
When calling auth functions, use these standard app identifiers:
| Application | Identifier | Description |
|---|
| AI Studio | ai-studio | AI conversation app |
| Desktop | desktop | Desktop application |
| Calendar | calendar | Calendar app |
| Photo Studio | photo-studio | Photo editing app |
| Custom App | Use project name | Use kebab-case project name |
Best Practices
- Error Handling: Always wrap auth calls in try-catch
- Loading States: Show loading UI during validation
- Offline Support: Allow graceful degradation when API unavailable
- Security:
- Never log tokens in production
- Never store JWT_SECRET in frontend (it belongs only on backend)
- Only use the token string from login response
- Token Refresh: Implement token refresh if using long sessions
- Testing: Test login flow in incognito/private mode
Backend Configuration Requirements
For integration to work, backend admin must:
-
Add your origin to CORS allowlist in backend .env:
ALLOWED_ORIGINS=http://localhost:5173,https://your-app.com
-
Verify these endpoints are accessible:
/auth (login page - public)
/api/auth/login (login API)
/api/auth/register (registration API)
/api/auth/me (user info API)
-
Backend JWT configuration (backend only, not shared with frontend):
JWT_SECRET=your-secret-key-kept-on-backend-only
JWT_EXPIRES_IN=7d
Note: JWT_SECRET stays on the backend server. Frontend never receives or configures this secret.
Additional Resources
For more detailed information, refer to the Forsion backend documentation:
- Complete API reference:
/docs/API.md
- Backend integration guide:
/docs/CLIENT_INTEGRATION.md
- Login system architecture:
/Documents/登录系统/统一登录系统实现方案.md
- SMS service setup:
/Documents/登录系统/阿里云短信服务配置指南.md
Quick Checklist
Before deploying:
Summary
This skill provides everything needed to integrate the Forsion unified login system:
- Copy
authRedirect.ts utility
- Call
initAuth() and validateAndRedirect() on app mount
- Configure API client with token interceptors
- Handle 401 errors with logout
- Test thoroughly
The integration enables seamless authentication across all Forsion applications with automatic token management and unified login experience.