Appearance
Token Login Page ​
File Information ​
- Path:
app/pages/[brand_name]/[token]/index.vue - Route:
/{brand_name}/{token} - Purpose: One-time token-based authentication page
- Layout: None (redirects immediately)
- Middleware: None (handles authentication internally)
Overview ​
The Token Login page handles one-time token-based authentication. Users with a valid token are automatically logged in and redirected to their dashboard. This page supports both public portal access and private workspace token login.
Key Features ​
Token Validation
- Validates token from route parameters
- Checks public portal access
- Handles password-less login
Authentication Flow
- Clears existing session
- Attempts public portal login first
- Falls back to token login
- Handles authentication errors
Brand Detection
- Detects brand name from route
- Handles subdomain vs main app URLs
- Formats brand name appropriately
Automatic Redirect
- Redirects to dashboard on success
- Redirects to login on failure
- Handles edge cases
Components Used ​
CollageLoader- Loading spinner component
Composables Used ​
useAuthStore()- Authentication storeuseAppDataStore()- Application data storeuseHelpers()- Helper functions
Data Properties ​
Route Parameters ​
typescript
{
brand_name: string, // Brand name from route
token: string // Authentication token from route
}Methods ​
getHostName() ​
Gets hostname and determines if using subdomain or main app.
typescript
function getHostName(): string {
const allowedDomains = [
"localhost:3001",
"localhost:3004",
"devdealer.collage.inc",
// ... other main app domains
]
if (import.meta.server) {
const headers = useRequestHeaders()
const host = headers?.host || ""
return allowedDomains.includes(host)
? brand_name
: host.split(":")[0]
}
const hostName = location.host
return allowedDomains.includes(hostName)
? brand_name
: hostName.split(":")[0]
}Purpose: Determines whether to use brand name from route or extract from subdomain.
handleLogin(email, password, workspace_id) ​
Handles standard email/password login.
typescript
async function handleLogin(
email: string,
password: string,
workspace_id: string
): Promise<boolean> {
const result = await authStore.login(email, password, workspace_id)
if (result.success && authStore.user) {
setCurrentWorkspace(String(authStore.user.workspace_id))
if (authStore.user.subscription_features) {
appStore.setSubscriptionFeatures(authStore.user.subscription_features)
}
return true
}
return false
}Flow:
- Calls auth store login
- Sets current workspace on success
- Sets subscription features
- Returns success status
handlePasswordLessLogin() ​
Handles password-less token login.
typescript
async function handlePasswordLessLogin(): Promise<boolean> {
try {
const hostName = getHostName()
const result = await authStore.loginWithToken(
token,
hostName,
formatBrandName(brand_name)
)
if (result.success && authStore.user) {
const workspace_id = String(authStore.user.workspace_id)
setCurrentWorkspace(workspace_id)
return true
} else {
console.error("Password-less login failed:", result.error)
return false
}
} catch (e) {
console.error("Password-less login failed:", e)
return false
}
}Flow:
- Gets hostname
- Calls token login with formatted brand name
- Sets current workspace on success
- Returns success status
checkPublicPortal() ​
Checks if brand is public portal and attempts auto-login.
typescript
async function checkPublicPortal(): Promise<string> {
try {
const response = await $api("check-public-portal", {
method: "POST",
body: { url: brand_name },
})
if (response.code === 200) {
const { workspace_id, email, password } = response.data
if (!workspace_id || !email || !password) {
return "index"
}
const success = await handleLogin(email, password, workspace_id)
return success ? "brand_name" : "login"
} else {
const success = await handlePasswordLessLogin()
return success ? "brand_name" : "login"
}
} catch (error) {
console.error("checkPublicPortal error:", error)
const success = await handlePasswordLessLogin()
return success ? "brand_name" : "login"
}
}Flow:
- Checks if brand is public portal
- If public, attempts auto-login with credentials
- If not public or auto-login fails, tries token login
- Returns redirect destination
User Interactions ​
None - page redirects immediately after authentication attempt.
Styling ​
- Shows loading spinner during authentication
- Minimal UI (redirects immediately)
Lifecycle Hooks ​
onMounted ​
Runs authentication flow on client-side only.
typescript
onMounted(async () => {
try {
// Clear any existing session before token login
await authStore.clearSession()
if (import.meta.client) {
sessionStorage.removeItem('anonymousCurrentBrand')
}
const redirect = await checkPublicPortal()
if (redirect === "brand_name") {
await navigateTo(`/${brand_name}`, { replace: true })
} else if (redirect === "index") {
await navigateTo(`/${getBrandName() || brand_name}`, { replace: true })
} else {
await navigateTo(`/${brand_name}/login`, { replace: true })
}
} catch (error) {
console.error("[Token Login] Error:", error)
await navigateTo(`/${brand_name}/login`, { replace: true })
}
})Flow:
- Clears existing session
- Removes anonymous brand from sessionStorage
- Checks public portal or token login
- Redirects based on result:
- Success → Dashboard
- Index → Brand home
- Failure → Login page
API Endpoints ​
Check Public Portal ​
- Endpoint:
POST /check-public-portal(via$api()) - Request Body:typescript
{ url: string // Brand name/URL } - Response:typescript
{ code: number, data?: { workspace_id: string, email: string, password: string } }
Token Login ​
- Endpoint:
POST /login-with-token(viaauthStore.loginWithToken()) - Request: Token, hostname, brand name
- Response: Authentication result with user data
User Flow ​
- User clicks token link (e.g.,
/{brand_name}/{token}) - Page loads and shows loading spinner
- System clears existing session
- System checks if brand is public portal:
- If public: Attempts auto-login with public credentials
- If not public: Attempts token login
- On success:
- User authenticated
- Redirect to dashboard
- On failure:
- Redirect to login page
- All redirects use
replace: trueto prevent back navigation
Security Considerations ​
- Token validated on server-side
- Existing session cleared before token login
- Token used only once (invalidated after use)
- Public portal credentials handled securely
- Session storage cleared for anonymous users
Notes for Development ​
- Page runs only on client-side (
onMounted) - Token from route parameters
- Brand name from route parameters
- Supports both subdomain and main app URLs
- Clears session before authentication
- Handles public portal auto-login
- Falls back to token login if public portal fails
- All redirects use
replace: true - Error handling redirects to login
Related Documentation ​
- Login Page - Standard login page
- Auth Store - Authentication store
- Global Auth Middleware - Global route protection
- Per-Route Auth Middleware - Per-route authentication