link-based-auth
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLink-Based Authentication
基于链接的身份验证
This is a reference pattern. Learn from the approach, adapt to your context — don't copy verbatim.
Problem: Some applications need personalized experiences without the friction of login screens — portfolio sites, demo apps, invite-only access.
Solution: Embed authentication tokens in personalized URLs. An edge function validates the token and sets a session cookie, giving the visitor a seamless authenticated experience.
这是一个参考模式。请学习该方法,结合你的实际场景适配使用,不要逐字照搬。
问题:部分应用需要提供个性化体验,但不想让用户受到登录页面的阻碍——比如作品集站点、演示应用、仅邀请访问的内容等。
解决方案:将身份验证令牌嵌入个性化URL中。边缘函数会验证令牌并设置会话Cookie,为访客提供无缝的已认证体验。
Pattern
模式
Flow:
1. Admin generates personalized link with embedded token
2. Visitor clicks link
3. CDN edge function intercepts request
4. Edge function validates token, sets auth cookie
5. Frontend loads with authentication already established
6. API requests use cookie/token for authorizationArchitecture:
Personalized Link (contains token)
↓
CDN Edge Function
├── Valid token → Set cookie, redirect to app
└── No/invalid token → Serve public view
↓
Static Frontend (reads cookie for auth state)
↓
API (validates token from Authorization header)Key Components:
- Link Generator — Creates URLs with embedded tokens, associates each with visitor metadata
- Identity Backend — Maps tokens to virtual user identities (e.g., Cognito users created without passwords)
- Edge Auth Function — Validates tokens at CDN edge, sets session cookies
- Auth Context — Frontend context that extracts tokens from cookies and provides auth state to components
流程:
1. Admin generates personalized link with embedded token
2. Visitor clicks link
3. CDN edge function intercepts request
4. Edge function validates token, sets auth cookie
5. Frontend loads with authentication already established
6. API requests use cookie/token for authorization架构:
Personalized Link (contains token)
↓
CDN Edge Function
├── Valid token → Set cookie, redirect to app
└── No/invalid token → Serve public view
↓
Static Frontend (reads cookie for auth state)
↓
API (validates token from Authorization header)核心组件:
- 链接生成器 — 创建嵌入令牌的URL,将每个令牌与访客元数据关联
- 身份后端 — 将令牌映射到虚拟用户身份(例如无需密码创建的Cognito用户)
- 边缘认证函数 — 在CDN边缘验证令牌,设置会话Cookie
- 认证上下文 — 前端上下文,从Cookie中提取令牌并为组件提供认证状态
Why This Pattern?
为什么选择该模式?
Benefits:
- Zero friction: No login screen, no password, no signup
- Personalized: Each link maps to a specific visitor identity
- Secure: Tokens are validated server-side, cookies are HttpOnly/Secure
- Tamper-proof: URL manipulation is ineffective — auth is tied to backend identities
Use Cases:
- Portfolio sites with recruiter-specific views
- Demo applications with invite-only access
- Marketing sites with gated personalized content
- Documentation with customer-specific sections
优势:
- 零使用阻碍:无需登录页面、无需密码、无需注册
- 个性化:每个链接对应特定的访客身份
- 安全性:令牌在服务端验证,Cookie设置为HttpOnly/Secure
- 防篡改:篡改URL无效,认证与后端身份绑定
适用场景:
- 面向招聘方提供专属视图的作品集站点
- 仅邀请访问的演示应用
- 设有门控个性化内容的营销站点
- 包含客户专属章节的文档
Implementation
实现方案
Edge Authentication
边缘身份认证
javascript
// Edge function (Lambda@Edge, CloudFlare Worker, etc.)
export async function handler(event) {
const request = event.Records[0].cf.request;
const params = new URLSearchParams(request.querystring);
const token = params.get('token');
if (token) {
const isValid = await validateToken(token);
if (isValid) {
return {
status: '302',
headers: {
'location': [{ value: '/' }],
'set-cookie': [{ value: `auth=${token}; Secure; HttpOnly; SameSite=Strict` }],
},
};
}
}
// Check existing session cookie
const cookies = request.headers.cookie?.[0]?.value || '';
if (cookies.includes('auth=')) {
return request; // Authenticated, proceed
}
return request; // Unauthenticated, serve public view
}javascript
// Edge function (Lambda@Edge, CloudFlare Worker, etc.)
export async function handler(event) {
const request = event.Records[0].cf.request;
const params = new URLSearchParams(request.querystring);
const token = params.get('token');
if (token) {
const isValid = await validateToken(token);
if (isValid) {
return {
status: '302',
headers: {
'location': [{ value: '/' }],
'set-cookie': [{ value: `auth=${token}; Secure; HttpOnly; SameSite=Strict` }],
},
};
}
}
// Check existing session cookie
const cookies = request.headers.cookie?.[0]?.value || '';
if (cookies.includes('auth=')) {
return request; // Authenticated, proceed
}
return request; // Unauthenticated, serve public view
}Frontend Auth Context
前端认证上下文
typescript
// lib/auth/auth-context.tsx
export function useAuth() {
const tokens = extractTokensFromCookies();
const environment = detectEnvironment();
const getAuthHeaders = (routeType: 'public' | 'protected') => {
if (environment === 'local') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (routeType === 'public') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (tokens.accessToken) {
return { Authorization: `Bearer ${tokens.accessToken}` };
}
return {};
};
return {
isAuthenticated: !!tokens.accessToken,
environment,
getAuthHeaders,
};
}typescript
// lib/auth/auth-context.tsx
export function useAuth() {
const tokens = extractTokensFromCookies();
const environment = detectEnvironment();
const getAuthHeaders = (routeType: 'public' | 'protected') => {
if (environment === 'local') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (routeType === 'public') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (tokens.accessToken) {
return { Authorization: `Bearer ${tokens.accessToken}` };
}
return {};
};
return {
isAuthenticated: !!tokens.accessToken,
environment,
getAuthHeaders,
};
}Hook-Based Data Fetching
基于Hook的数据请求
typescript
// lib/profile/use-profile.ts
export function useProfile() {
const { isAuthenticated, getAuthHeaders } = useAuth();
const { data, loading } = useQuery(GET_PROFILE, {
skip: !isAuthenticated,
context: { headers: getAuthHeaders('protected') },
});
return { profile: data?.profile, isLoading: loading };
}typescript
// lib/profile/use-profile.ts
export function useProfile() {
const { isAuthenticated, getAuthHeaders } = useAuth();
const { data, loading } = useQuery(GET_PROFILE, {
skip: !isAuthenticated,
context: { headers: getAuthHeaders('protected') },
});
return { profile: data?.profile, isLoading: loading };
}Security Considerations
安全注意事项
- Cookie flags: ,
Secure,HttpOnly— prevents XSS and CSRFSameSite=Strict - Token expiration: Short-lived tokens limit exposure window
- Content isolation: Each token maps to a specific identity — visitors only see their content
- No client-side secrets: Tokens are validated server-side; frontend only reads the result
- Direct URL access: Without a valid token/cookie, only public content is visible (by design)
- Cookie标识:设置、
Secure、HttpOnly— 防范XSS和CSRF攻击SameSite=Strict - 令牌过期:使用短期令牌缩小泄露风险窗口
- 内容隔离:每个令牌对应特定身份,访客只能查看自己的内容
- 无客户端密钥:令牌在服务端验证,前端仅读取验证结果
- 直接URL访问:设计上无有效令牌/Cookie时仅能查看公开内容
Local Development
本地开发
For local development without real tokens, use an environment-aware interceptor:
typescript
export function useLocalInterceptor() {
const { environment } = useAuth();
const visitorId = useSearchParams()?.get('visitor');
const shouldIntercept = environment === 'local' && !!visitorId;
return {
shouldIntercept,
getMockData: () => ({
name: `Test Visitor ${visitorId}`,
message: 'Mock data for local development',
}),
};
}Components check the interceptor first, falling back to real data:
typescript
function PersonalizedContent() {
const interceptor = useLocalInterceptor();
const { data } = useRealData();
const content = interceptor.shouldIntercept
? interceptor.getMockData()
: data;
return <div>{content.message}</div>;
}本地开发无需使用真实令牌,可以使用环境感知拦截器:
typescript
export function useLocalInterceptor() {
const { environment } = useAuth();
const visitorId = useSearchParams()?.get('visitor');
const shouldIntercept = environment === 'local' && !!visitorId;
return {
shouldIntercept,
getMockData: () => ({
name: `Test Visitor ${visitorId}`,
message: 'Mock data for local development',
}),
};
}组件优先检查拦截器, fallback到真实数据:
typescript
function PersonalizedContent() {
const interceptor = useLocalInterceptor();
const { data } = useRealData();
const content = interceptor.shouldIntercept
? interceptor.getMockData()
: data;
return <div>{content.message}</div>;
}When NOT to Use
不适用场景
- Sensitive data: This pattern is for personalization, not for protecting highly sensitive information
- Long-lived sessions: Token-based links are best for short interactions, not persistent accounts
- Complex auth flows: If you need MFA, password reset, or role management, use a full auth provider
- 敏感数据场景:该模式用于个性化需求,不适合保护高度敏感的信息
- 长期会话场景:基于令牌的链接最适合短时间交互,不适合持久化账户
- 复杂认证流程场景:如果你需要MFA、密码重置或角色管理功能,请使用完整的认证服务商方案
Related Patterns
相关模式
- Static Frontend Hosting — The hosting infrastructure this auth pattern runs on
- Environment Deployment Strategy — How environments affect auth configuration
- 静态前端托管 — 该认证模式运行的宿主基础设施
- 环境部署策略 — 不同环境对认证配置的影响
Progressive Improvement
持续优化
If the developer corrects a behavior that this skill should have prevented, suggest a specific amendment to this skill to prevent the same correction in the future.
如果开发者修正了本技能本应预防的错误行为,请提出对本技能的具体修改建议,避免未来再次出现同类修正。