spider-weave
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpider Weave 🕷️
蜘蛛织网 🕷️
The spider doesn't rush. It spins one thread at a time, anchoring each carefully before moving to the next. The web grows organically—radial strands first, then the spiral, each connection tested for strength. When complete, the web catches what matters while letting the wind pass through. Authentication woven this way is strong, resilient, and beautiful in its structure.
蜘蛛从不急躁。它一次纺制一根丝线,在移动到下一根之前仔细固定每一个锚点。网络有机生长——先放射状丝线,再螺旋状丝线,每一处连接都经过强度测试。完成后的蛛网能捕获重要的事物,同时让风穿过。以这种方式编织的认证系统坚固、有韧性,结构优美。
When to Activate
激活场景
- User asks to "add auth" or "set up authentication"
- User says "protect this route" or "add login"
- User calls or mentions spider/auth
/spider-weave - Integrating OAuth (Google, GitHub, etc.)
- Setting up session management
- Protecting API routes
- Adding role-based access control (RBAC)
- Implementing PKCE flow
- Connecting to Heartwood (GroveAuth)
Pair with: for security review, for auth testing
raccoon-auditbeaver-build- 用户要求“添加认证”或“搭建认证系统”
- 用户提到“保护该路由”或“添加登录功能”
- 用户调用或提及spider/auth
/spider-weave - 集成OAuth(Google、GitHub等)
- 搭建会话管理
- 保护API路由
- 添加基于角色的访问控制(RBAC)
- 实现PKCE流程
- 连接Heartwood(GroveAuth)
搭配使用: 用于安全审查,用于认证测试
raccoon-auditbeaver-buildThe Weave
织网流程
SPIN → CONNECT → SECURE → TEST → BIND
↓ ↓ ↓ ↓ ↓
Create Link Harden Verify Lock In
Threads Strands Knots Web SecuritySPIN → CONNECT → SECURE → TEST → BIND
↓ ↓ ↓ ↓ ↓
Create Link Harden Verify Lock In
Threads Strands Knots Web SecurityPhase 1: SPIN
阶段1:SPIN(纺制)
The spider spins the first thread, anchoring it carefully...
Create the foundational auth structure:
Choose the Auth Pattern:
| Pattern | Best For | Complexity |
|---|---|---|
| Session-based | Traditional web apps | Medium |
| JWT | Stateless APIs, SPAs | Medium |
| OAuth 2.0 | Third-party login | High |
| PKCE | Mobile/SPA OAuth | High |
| API Keys | Service-to-service | Low |
For Grove/Heartwood Integration:
typescript
// PKCE flow setup
import { generatePKCE } from '$lib/auth/pkce';
const { codeVerifier, codeChallenge } = await generatePKCE();
// Store verifier (cookie or session)
cookies.set('pkce_verifier', codeVerifier, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 600 // 10 minutes
});
// Redirect to Heartwood
const authUrl = new URL('https://heartwood.grove.place/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('state', generateState());Core Auth Files Structure:
src/lib/auth/
├── index.ts # Main exports
├── types.ts # Auth-related types
├── session.ts # Session management
├── middleware.ts # Route protection
├── pkce.ts # PKCE utilities
└── client.ts # Heartwood/OAuth clientDatabase Schema:
typescript
// Users table (linked to Heartwood)
export const users = sqliteTable('users', {
id: integer('id').primaryKey(),
heartwoodId: text('heartwood_id').unique(),
email: text('email').unique(),
displayName: text('display_name'),
avatarUrl: text('avatar_url'),
role: text('role').default('user'), // admin, user, guest
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});
// Sessions (if using session-based auth)
export const sessions = sqliteTable('sessions', {
id: text('id').primaryKey(),
userId: integer('user_id').references(() => users.id),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
});Environment Variables:
bash
undefined蜘蛛纺制第一根丝线,仔细固定锚点...
创建基础认证结构:
选择认证模式:
| 模式 | 最佳适用场景 | 复杂度 |
|---|---|---|
| 基于会话 | 传统Web应用 | 中等 |
| JWT | 无状态API、单页应用(SPA) | 中等 |
| OAuth 2.0 | 第三方登录 | 高 |
| PKCE | 移动应用/SPA的OAuth | 高 |
| API密钥 | 服务间通信 | 低 |
Grove/Heartwood集成示例:
typescript
// PKCE flow setup
import { generatePKCE } from '$lib/auth/pkce';
const { codeVerifier, codeChallenge } = await generatePKCE();
// Store verifier (cookie or session)
cookies.set('pkce_verifier', codeVerifier, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 600 // 10 minutes
});
// Redirect to Heartwood
const authUrl = new URL('https://heartwood.grove.place/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('state', generateState());核心认证文件结构:
src/lib/auth/
├── index.ts # 主导出文件
├── types.ts # 认证相关类型定义
├── session.ts # 会话管理
├── middleware.ts # 路由保护中间件
├── pkce.ts # PKCE工具类
└── client.ts # Heartwood/OAuth客户端数据库Schema:
typescript
// Users table (linked to Heartwood)
export const users = sqliteTable('users', {
id: integer('id').primaryKey(),
heartwoodId: text('heartwood_id').unique(),
email: text('email').unique(),
displayName: text('display_name'),
avatarUrl: text('avatar_url'),
role: text('role').default('user'), // admin, user, guest
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});
// Sessions (if using session-based auth)
export const sessions = sqliteTable('sessions', {
id: text('id').primaryKey(),
userId: integer('user_id').references(() => users.id),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
});环境变量:
bash
undefinedOAuth/Heartwood
OAuth/Heartwood
HEARTWOOD_CLIENT_ID=
HEARTWOOD_CLIENT_SECRET=
HEARTWOOD_AUTHORIZE_URL=https://heartwood.grove.place/oauth/authorize
HEARTWOOD_TOKEN_URL=https://heartwood.grove.place/oauth/token
HEARTWOOD_USERINFO_URL=https://heartwood.grove.place/oauth/userinfo
HEARTWOOD_CLIENT_ID=
HEARTWOOD_CLIENT_SECRET=
HEARTWOOD_AUTHORIZE_URL=https://heartwood.grove.place/oauth/authorize
HEARTWOOD_TOKEN_URL=https://heartwood.grove.place/oauth/token
HEARTWOOD_USERINFO_URL=https://heartwood.grove.place/oauth/userinfo
App
App
AUTH_REDIRECT_URI=http://localhost:5173/auth/callback
SESSION_SECRET=generate_with_openssl_rand_hex_32
**Output:** Auth infrastructure created, dependencies installed, schema defined
---AUTH_REDIRECT_URI=http://localhost:5173/auth/callback
SESSION_SECRET=generate_with_openssl_rand_hex_32
**输出结果:** 认证基础设施创建完成、依赖安装完毕、Schema定义完成
---Phase 2: CONNECT
阶段2:CONNECT(连接)
Thread connects to thread, the web taking shape...
Link the auth system together:
OAuth Flow Implementation:
typescript
// 1. Login route - redirect to provider
// src/routes/auth/login/+server.ts
export const GET: RequestHandler = async () => {
const { codeVerifier, codeChallenge } = generatePKCE();
const state = generateState();
// Store PKCE verifier
cookies.set('pkce_verifier', codeVerifier, { httpOnly: true, secure: true });
cookies.set('oauth_state', state, { httpOnly: true, secure: true });
const url = new URL(HEARTWOOD_AUTHORIZE_URL);
url.searchParams.set('client_id', HEARTWOOD_CLIENT_ID);
url.searchParams.set('code_challenge', codeChallenge);
url.searchParams.set('code_challenge_method', 'S256');
url.searchParams.set('redirect_uri', AUTH_REDIRECT_URI);
url.searchParams.set('state', state);
throw redirect(302, url.toString());
};
// 2. Callback route - handle OAuth response
// src/routes/auth/callback/+server.ts
export const GET: RequestHandler = async ({ url, cookies }) => {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const storedState = cookies.get('oauth_state');
// Verify state (CSRF protection)
if (state !== storedState) {
throw error(400, 'Invalid state parameter');
}
// Exchange code for tokens
const tokenResponse = await fetch(HEARTWOOD_TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: HEARTWOOD_CLIENT_ID,
client_secret: HEARTWOOD_CLIENT_SECRET,
code: code!,
code_verifier: cookies.get('pkce_verifier')!,
redirect_uri: AUTH_REDIRECT_URI,
}),
});
const tokens = await tokenResponse.json();
// Get user info
const userResponse = await fetch(HEARTWOOD_USERINFO_URL, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const userInfo = await userResponse.json();
// Create/update user in database
const user = await upsertUser({
heartwoodId: userInfo.sub,
email: userInfo.email,
displayName: userInfo.name,
avatarUrl: userInfo.picture,
});
// Create session
const session = await createSession(user.id);
// Set session cookie
cookies.set('session', session.id, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 days
});
// Clean up PKCE cookies
cookies.delete('pkce_verifier');
cookies.delete('oauth_state');
throw redirect(302, '/dashboard');
};Session Management:
typescript
// src/lib/auth/session.ts
export async function createSession(userId: number): Promise<Session> {
const sessionId = generateSecureId();
const expiresAt = new Date(Date.now() + SESSION_DURATION);
await db.insert(sessions).values({
id: sessionId,
userId,
expiresAt,
});
return { id: sessionId, userId, expiresAt };
}
export async function validateSession(sessionId: string): Promise<User | null> {
const session = await db.query.sessions.findFirst({
where: eq(sessions.id, sessionId),
with: { user: true },
});
if (!session || session.expiresAt < new Date()) {
return null;
}
return session.user;
}
export async function invalidateSession(sessionId: string): Promise<void> {
await db.delete(sessions).where(eq(sessions.id, sessionId));
}Auth Store (Client-side):
typescript
// src/lib/stores/auth.ts
import { writable } from 'svelte/store';
export interface AuthState {
user: User | null;
loading: boolean;
}
export const auth = writable<AuthState>({
user: null,
loading: true,
});
export async function loadUser() {
const response = await fetch('/api/auth/me');
if (response.ok) {
const user = await response.json();
auth.set({ user, loading: false });
} else {
auth.set({ user: null, loading: false });
}
}Output: OAuth flow connected, session management working, client state ready
丝线与丝线相连,网络逐渐成型...
将认证系统各部分连接起来:
OAuth流程实现:
typescript
// 1. 登录路由 - 重定向到认证提供商
// src/routes/auth/login/+server.ts
export const GET: RequestHandler = async () => {
const { codeVerifier, codeChallenge } = generatePKCE();
const state = generateState();
// 存储PKCE验证器
cookies.set('pkce_verifier', codeVerifier, { httpOnly: true, secure: true });
cookies.set('oauth_state', state, { httpOnly: true, secure: true });
const url = new URL(HEARTWOOD_AUTHORIZE_URL);
url.searchParams.set('client_id', HEARTWOOD_CLIENT_ID);
url.searchParams.set('code_challenge', codeChallenge);
url.searchParams.set('code_challenge_method', 'S256');
url.searchParams.set('redirect_uri', AUTH_REDIRECT_URI);
url.searchParams.set('state', state);
throw redirect(302, url.toString());
};
// 2. 回调路由 - 处理OAuth响应
// src/routes/auth/callback/+server.ts
export const GET: RequestHandler = async ({ url, cookies }) => {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const storedState = cookies.get('oauth_state');
// 验证state(CSRF防护)
if (state !== storedState) {
throw error(400, 'Invalid state parameter');
}
// 用授权码交换令牌
const tokenResponse = await fetch(HEARTWOOD_TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: HEARTWOOD_CLIENT_ID,
client_secret: HEARTWOOD_CLIENT_SECRET,
code: code!,
code_verifier: cookies.get('pkce_verifier')!,
redirect_uri: AUTH_REDIRECT_URI,
}),
});
const tokens = await tokenResponse.json();
// 获取用户信息
const userResponse = await fetch(HEARTWOOD_USERINFO_URL, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const userInfo = await userResponse.json();
// 在数据库中创建/更新用户
const user = await upsertUser({
heartwoodId: userInfo.sub,
email: userInfo.email,
displayName: userInfo.name,
avatarUrl: userInfo.picture,
});
// 创建会话
const session = await createSession(user.id);
// 设置会话Cookie
cookies.set('session', session.id, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7天
});
// 清理PKCE相关Cookie
cookies.delete('pkce_verifier');
cookies.delete('oauth_state');
throw redirect(302, '/dashboard');
};会话管理:
typescript
// src/lib/auth/session.ts
export async function createSession(userId: number): Promise<Session> {
const sessionId = generateSecureId();
const expiresAt = new Date(Date.now() + SESSION_DURATION);
await db.insert(sessions).values({
id: sessionId,
userId,
expiresAt,
});
return { id: sessionId, userId, expiresAt };
}
export async function validateSession(sessionId: string): Promise<User | null> {
const session = await db.query.sessions.findFirst({
where: eq(sessions.id, sessionId),
with: { user: true },
});
if (!session || session.expiresAt < new Date()) {
return null;
}
return session.user;
}
export async function invalidateSession(sessionId: string): Promise<void> {
await db.delete(sessions).where(eq(sessions.id, sessionId));
}客户端认证Store:
typescript
// src/lib/stores/auth.ts
import { writable } from 'svelte/store';
export interface AuthState {
user: User | null;
loading: boolean;
}
export const auth = writable<AuthState>({
user: null,
loading: true,
});
export async function loadUser() {
const response = await fetch('/api/auth/me');
if (response.ok) {
const user = await response.json();
auth.set({ user, loading: false });
} else {
auth.set({ user: null, loading: false });
}
}输出结果: OAuth流程连接完成、会话管理正常工作、客户端状态就绪
Phase 3: SECURE
阶段3:SECURE(加固)
The spider tests each knot, ensuring the web holds...
Harden the authentication system:
Route Protection:
typescript
// src/lib/auth/middleware.ts
export function requireAuth(): Handle {
return async ({ event, resolve }) => {
const sessionId = event.cookies.get('session');
if (!sessionId) {
throw redirect(302, '/auth/login');
}
const user = await validateSession(sessionId);
if (!user) {
event.cookies.delete('session');
throw redirect(302, '/auth/login');
}
event.locals.user = user;
return resolve(event);
};
}
// Role-based protection
export function requireRole(allowedRoles: string[]): Handle {
return async ({ event, resolve }) => {
const user = event.locals.user;
if (!user || !allowedRoles.includes(user.role)) {
throw error(403, 'Forbidden');
}
return resolve(event);
};
}Security Headers:
typescript
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
// Security headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);
return response;
};CSRF Protection:
typescript
// For state-changing operations
export function validateCSRF(event: RequestEvent): void {
const origin = event.request.headers.get('origin');
const host = event.url.host;
if (origin && new URL(origin).host !== host) {
throw error(403, 'Invalid origin');
}
}Rate Limiting:
typescript
// src/lib/auth/rate-limit.ts
const attempts = new Map<string, number[]>();
export function checkRateLimit(identifier: string, maxAttempts: number = 5): boolean {
const now = Date.now();
const windowStart = now - 15 * 60 * 1000; // 15 minutes
const userAttempts = attempts.get(identifier) || [];
const recentAttempts = userAttempts.filter(t => t > windowStart);
if (recentAttempts.length >= maxAttempts) {
return false;
}
recentAttempts.push(now);
attempts.set(identifier, recentAttempts);
return true;
}Secure Cookie Settings:
typescript
// Always use these for auth cookies
{
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only in production
sameSite: 'lax', // CSRF protection
maxAge: 604800, // 7 days
path: '/', // Available site-wide
}Output: Routes protected, headers set, rate limiting active, security hardened
蜘蛛测试每个节点,确保蛛网牢固...
加固认证系统:
路由保护:
typescript
// src/lib/auth/middleware.ts
export function requireAuth(): Handle {
return async ({ event, resolve }) => {
const sessionId = event.cookies.get('session');
if (!sessionId) {
throw redirect(302, '/auth/login');
}
const user = await validateSession(sessionId);
if (!user) {
event.cookies.delete('session');
throw redirect(302, '/auth/login');
}
event.locals.user = user;
return resolve(event);
};
}
// 基于角色的保护
export function requireRole(allowedRoles: string[]): Handle {
return async ({ event, resolve }) => {
const user = event.locals.user;
if (!user || !allowedRoles.includes(user.role)) {
throw error(403, 'Forbidden');
}
return resolve(event);
};
}安全Headers:
typescript
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
// 安全Headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);
return response;
};CSRF防护:
typescript
// 针对状态变更操作
export function validateCSRF(event: RequestEvent): void {
const origin = event.request.headers.get('origin');
const host = event.url.host;
if (origin && new URL(origin).host !== host) {
throw error(403, 'Invalid origin');
}
}速率限制:
typescript
// src/lib/auth/rate-limit.ts
const attempts = new Map<string, number[]>();
export function checkRateLimit(identifier: string, maxAttempts: number = 5): boolean {
const now = Date.now();
const windowStart = now - 15 * 60 * 1000; // 15分钟
const userAttempts = attempts.get(identifier) || [];
const recentAttempts = userAttempts.filter(t => t > windowStart);
if (recentAttempts.length >= maxAttempts) {
return false;
}
recentAttempts.push(now);
attempts.set(identifier, recentAttempts);
return true;
}安全Cookie设置:
typescript
// 认证Cookie始终使用以下配置
{
httpOnly: true, // 无法通过JavaScript访问
secure: true, // 生产环境仅通过HTTPS传输
sameSite: 'lax', // CSRF防护
maxAge: 604800, // 7天
path: '/', // 全站可用
}输出结果: 路由已保护、Headers已设置、速率限制已激活、安全加固完成
Phase 4: TEST
阶段4:TEST(测试)
The spider plucks the strands, verifying the web vibrates true...
Test authentication thoroughly:
Test Coverage:
typescript
// tests/auth/oauth.test.ts
describe('OAuth Flow', () => {
test('redirects to Heartwood with PKCE', async () => {
const response = await request(app).get('/auth/login');
expect(response.status).toBe(302);
expect(response.headers.location).toMatch(/heartwood\.grove\.place/);
expect(response.headers.location).toMatch(/code_challenge=/);
});
test('handles callback and creates session', async () => {
// Mock Heartwood responses
mockHeartwoodTokenEndpoint({ access_token: 'test-token' });
mockHeartwoodUserInfo({ sub: '123', email: 'test@example.com' });
const response = await request(app)
.get('/auth/callback?code=valid-code&state=valid-state')
.set('Cookie', ['oauth_state=valid-state; pkce_verifier=test-verifier']);
expect(response.status).toBe(302);
expect(response.headers.location).toBe('/dashboard');
// Verify session created
const cookies = response.headers['set-cookie'];
expect(cookies).toMatch(/session=/);
});
test('rejects invalid state (CSRF protection)', async () => {
const response = await request(app)
.get('/auth/callback?code=valid-code&state=wrong-state')
.set('Cookie', ['oauth_state=correct-state']);
expect(response.status).toBe(400);
});
});
describe('Route Protection', () => {
test('redirects unauthenticated users', async () => {
const response = await request(app).get('/dashboard');
expect(response.status).toBe(302);
expect(response.headers.location).toBe('/auth/login');
});
test('allows authenticated users', async () => {
const session = await createTestUserAndSession();
const response = await request(app)
.get('/dashboard')
.set('Cookie', [`session=${session.id}`]);
expect(response.status).toBe(200);
});
test('enforces role restrictions', async () => {
const user = await createTestUser({ role: 'user' });
const session = await createSession(user.id);
const response = await request(app)
.get('/admin')
.set('Cookie', [`session=${session.id}`]);
expect(response.status).toBe(403);
});
});Security Testing:
typescript
// Test session fixation
test('session ID changes after login', async () => {
const oldSession = cookies.get('session');
await completeLoginFlow();
const newSession = cookies.get('session');
expect(newSession).not.toBe(oldSession);
});
// Test cookie security
test('auth cookies have secure attributes', async () => {
const response = await completeLoginFlow();
const cookies = response.headers['set-cookie'];
expect(cookies).toMatch(/HttpOnly/);
expect(cookies).toMatch(/SameSite=/);
});Output: All auth flows tested, security verified, edge cases covered
蜘蛛拨动丝线,验证蛛网振动正常...
全面测试认证系统:
测试覆盖:
typescript
// tests/auth/oauth.test.ts
describe('OAuth Flow', () => {
test('redirects to Heartwood with PKCE', async () => {
const response = await request(app).get('/auth/login');
expect(response.status).toBe(302);
expect(response.headers.location).toMatch(/heartwood\.grove\.place/);
expect(response.headers.location).toMatch(/code_challenge=/);
});
test('handles callback and creates session', async () => {
// 模拟Heartwood响应
mockHeartwoodTokenEndpoint({ access_token: 'test-token' });
mockHeartwoodUserInfo({ sub: '123', email: 'test@example.com' });
const response = await request(app)
.get('/auth/callback?code=valid-code&state=valid-state')
.set('Cookie', ['oauth_state=valid-state; pkce_verifier=test-verifier']);
expect(response.status).toBe(302);
expect(response.headers.location).toBe('/dashboard');
// 验证会话已创建
const cookies = response.headers['set-cookie'];
expect(cookies).toMatch(/session=/);
});
test('rejects invalid state (CSRF protection)', async () => {
const response = await request(app)
.get('/auth/callback?code=valid-code&state=wrong-state')
.set('Cookie', ['oauth_state=correct-state']);
expect(response.status).toBe(400);
});
});
describe('Route Protection', () => {
test('redirects unauthenticated users', async () => {
const response = await request(app).get('/dashboard');
expect(response.status).toBe(302);
expect(response.headers.location).toBe('/auth/login');
});
test('allows authenticated users', async () => {
const session = await createTestUserAndSession();
const response = await request(app)
.get('/dashboard')
.set('Cookie', [`session=${session.id}`]);
expect(response.status).toBe(200);
});
test('enforces role restrictions', async () => {
const user = await createTestUser({ role: 'user' });
const session = await createSession(user.id);
const response = await request(app)
.get('/admin')
.set('Cookie', [`session=${session.id}`]);
expect(response.status).toBe(403);
});
});安全测试:
typescript
// 测试会话固定攻击
test('session ID changes after login', async () => {
const oldSession = cookies.get('session');
await completeLoginFlow();
const newSession = cookies.get('session');
expect(newSession).not.toBe(oldSession);
});
// 测试Cookie安全性
test('auth cookies have secure attributes', async () => {
const response = await completeLoginFlow();
const cookies = response.headers['set-cookie'];
expect(cookies).toMatch(/HttpOnly/);
expect(cookies).toMatch(/SameSite=/);
});输出结果: 所有认证流程测试完成、安全验证通过、边缘场景覆盖
Phase 5: BIND
阶段5:BIND(绑定)
The web is complete, each strand bound tight, ready to catch what comes...
Finalize and lock in the authentication:
Integration Checklist:
- Login flow works end-to-end
- Logout clears session
- Protected routes redirect unauthenticated users
- Session expires correctly
- Token refresh works (if applicable)
- Error messages don't leak sensitive info
- Rate limiting prevents brute force
- CSRF protection active
- Security headers set
- Cookies configured securely
User Experience Polish:
svelte
<!-- Login button states -->
<script>
let loading = $state(false);
let error = $state('');
</script>
{#if error}
<div role="alert" class="error">
{error}
</div>
{/if}
<button
on:click={handleLogin}
disabled={loading}
aria-busy={loading}
>
{#if loading}
<span class="spinner" aria-hidden="true" />
Connecting...
{:else}
Sign in with Heartwood
{/if}
</button>Monitoring & Logging:
typescript
// Log auth events (without sensitive data)
logger.info('User authenticated', {
userId: user.id,
provider: 'heartwood',
ip: event.getClientAddress(),
});
// Alert on suspicious activity
if (failedAttempts > 10) {
logger.warn('Potential brute force attack', {
identifier,
attempts: failedAttempts,
});
}Documentation:
markdown
undefined蛛网已完成,每根丝线都紧密绑定,准备好迎接一切...
最终完成并锁定认证系统:
集成检查清单:
- 登录流程端到端正常工作
- 登出操作会清除会话
- 受保护路由会重定向未认证用户
- 会话会正确过期
- 令牌刷新功能正常(如适用)
- 错误信息不会泄露敏感信息
- 速率限制可防止暴力破解
- CSRF防护已激活
- 安全Headers已设置
- Cookie配置安全
用户体验优化:
svelte
<!-- 登录按钮状态 -->
<script>
let loading = $state(false);
let error = $state('');
</script>
{#if error}
<div role="alert" class="error">
{error}
</div>
{/if}
<button
on:click={handleLogin}
disabled={loading}
aria-busy={loading}
>
{#if loading}
<span class="spinner" aria-hidden="true" />
连接中...
{:else}
使用Heartwood登录
{/if}
</button>监控与日志:
typescript
// 记录认证事件(不包含敏感数据)
logger.info('User authenticated', {
userId: user.id,
provider: 'heartwood',
ip: event.getClientAddress(),
});
// 对可疑活动发出警报
if (failedAttempts > 10) {
logger.warn('Potential brute force attack', {
identifier,
attempts: failedAttempts,
});
}文档:
markdown
undefinedAuthentication System
认证系统
Architecture
架构
- OAuth 2.0 with PKCE for secure token exchange
- Session-based auth for web app
- Heartwood (GroveAuth) as identity provider
- 采用OAuth 2.0 + PKCE实现安全令牌交换
- Web应用使用基于会话的认证
- 身份提供商为Heartwood(GroveAuth)
Flow
流程
- User clicks "Sign in" → Redirect to Heartwood
- User authenticates with Heartwood
- Heartwood redirects back with auth code
- App exchanges code for tokens
- App creates session, sets cookie
- User is authenticated
- 用户点击“登录” → 重定向到Heartwood
- 用户在Heartwood完成认证
- Heartwood重定向回应用并携带授权码
- 应用用授权码交换令牌
- 应用创建会话并设置Cookie
- 用户完成认证
Protected Routes
受保护路由
Add to :
src/routes/protected/+page.server.tstypescript
export const load = async ({ locals }) => {
if (!locals.user) {
throw redirect(302, '/auth/login');
}
return { user: locals.user };
};在中添加:
src/routes/protected/+page.server.tstypescript
export const load = async ({ locals }) => {
if (!locals.user) {
throw redirect(302, '/auth/login');
}
return { user: locals.user };
};Environment Variables
环境变量
See for required variables.
.env.example
**Completion Report:**
```markdown参考获取所需变量。
.env.example
**完成报告:**
```markdown🕷️ SPIDER WEAVE COMPLETE
🕷️ 蜘蛛织网完成
Auth System Integrated
认证系统集成情况
- Provider: Heartwood (GroveAuth)
- Flow: OAuth 2.0 + PKCE
- Session: Cookie-based, 7-day expiry
- 提供商:Heartwood(GroveAuth)
- 流程:OAuth 2.0 + PKCE
- 会话:基于Cookie,7天有效期
Files Created
创建的文件
- (6 files)
src/lib/auth/ src/routes/auth/login/+server.tssrc/routes/auth/callback/+server.tssrc/routes/auth/logout/+server.tssrc/lib/stores/auth.ts
- (6个文件)
src/lib/auth/ src/routes/auth/login/+server.tssrc/routes/auth/callback/+server.tssrc/routes/auth/logout/+server.tssrc/lib/stores/auth.ts
Security Features
安全特性
- ✅ PKCE for OAuth
- ✅ CSRF protection
- ✅ Rate limiting (5 attempts / 15 min)
- ✅ Secure cookie attributes
- ✅ Security headers
- ✅ Role-based access control
- ✅ OAuth采用PKCE
- ✅ CSRF防护
- ✅ 速率限制(15分钟内最多5次尝试)
- ✅ Cookie安全配置
- ✅ 安全Headers
- ✅ 基于角色的访问控制
Tests
测试情况
- 15 unit tests
- 8 integration tests
- 100% pass rate
The web is woven. The system is secure. 🕷️
**Output:** Auth system complete, tested, documented, monitoring in place
---- 15个单元测试
- 8个集成测试
- 100%通过率
蛛网已织就,系统安全就绪。 🕷️
**输出结果:** 认证系统完成、测试通过、文档齐全、监控已部署
---Spider Rules
蜘蛛准则
Patience
耐心
Weave one thread at a time. Don't rush to connect everything at once. Each strand must be secure before adding the next.
一次纺制一根丝线。不要急于连接所有部分。每根丝线都必须先固定牢固,再添加下一根。
Precision
精准
Small mistakes in auth have big consequences. Verify every redirect, check every token, validate every session.
认证中的小错误会导致严重后果。验证每一次重定向、检查每一个令牌、确认每一个会话。
Completeness
完整
A web with holes catches nothing. Test the error paths, the edge cases, the failure modes. Security is only as strong as the weakest strand.
有漏洞的蛛网什么也捕获不到。测试错误路径、边缘场景、失败模式。安全强度取决于最薄弱的环节。
Communication
沟通
Use weaving metaphors:
- "Spinning the threads..." (creating foundations)
- "Connecting the strands..." (linking components)
- "Testing the knots..." (security hardening)
- "The web holds..." (verification complete)
使用织网相关的隐喻:
- “纺制丝线中...”(创建基础结构)
- “连接链路中...”(关联组件)
- “测试节点中...”(安全加固)
- “蛛网牢固...”(验证完成)
Anti-Patterns
反模式
The spider does NOT:
- Store passwords in plain text (ever)
- Skip PKCE in OAuth flows
- Trust user input without validation
- Leave default secrets in configuration
- Ignore session expiration
- Log sensitive data (tokens, passwords)
蜘蛛绝不会:
- 明文存储密码(永远不要)
- 在OAuth流程中跳过PKCE
- 未经验证就信任用户输入
- 在配置中保留默认密钥
- 忽略会话过期
- 记录敏感数据(令牌、密码)
Example Weave
织网示例
User: "Add GitHub OAuth login"
Spider flow:
-
🕷️ SPIN — "Create OAuth app in GitHub, generate client credentials, set up PKCE utilities, create auth endpoints structure"
-
🕷️ CONNECT — "Implement /auth/github/login redirect, /auth/github/callback handler, user upsert logic, session creation"
-
🕷️ SECURE — "Add CSRF state validation, secure cookie settings, rate limiting on auth endpoints, role assignment for new users"
-
🕷️ TEST — "Test OAuth flow, callback handling, session creation, protected route access, error cases (denied permissions)"
-
🕷️ BIND — "Add login button to UI, error state handling, loading states, documentation, monitoring"
用户需求: “添加GitHub OAuth登录”
蜘蛛流程:
-
🕷️ SPIN — “在GitHub创建OAuth应用、生成客户端凭证、搭建PKCE工具类、创建认证端点结构”
-
🕷️ CONNECT — “实现/auth/github/login重定向、/auth/github/callback处理器、用户更新逻辑、会话创建”
-
🕷️ SECURE — “添加CSRF状态验证、安全Cookie配置、认证端点速率限制、新用户角色分配”
-
🕷️ TEST — “测试OAuth流程、回调处理、会话创建、受保护路由访问、错误场景(权限被拒绝)”
-
🕷️ BIND — “在UI中添加登录按钮、错误状态处理、加载状态、文档、监控”
Quick Decision Guide
快速决策指南
| Situation | Approach |
|---|---|
| Simple app, internal users | Session-based auth |
| Public app, social login | OAuth 2.0 + PKCE |
| API for mobile/SPA | JWT with refresh tokens |
| Service-to-service | API keys with IP allowlist |
| Grove ecosystem | Heartwood integration |
| 场景 | 方案 |
|---|---|
| 简单应用、内部用户 | 基于会话的认证 |
| 公开应用、社交登录 | OAuth 2.0 + PKCE |
| 面向移动/SPA的API | 带刷新令牌的JWT |
| 服务间通信 | 带IP白名单的API密钥 |
| Grove生态系统 | Heartwood集成 |
Integration with Other Skills
与其他技能的集成
Before Weaving:
- — For auth system design
eagle-architect - — For auth flow specifications
swan-design
During Weaving:
- — For multi-file auth implementation
elephant-build - — For security review
raccoon-audit
After Weaving:
- — For auth testing
beaver-build - — For accessibility audit of login UI
deer-sense
A well-woven web catches intruders while letting friends pass through. 🕷️
织网前:
- — 用于认证系统设计
eagle-architect - — 用于认证流程规范
swan-design
织网中:
- — 用于多文件认证实现
elephant-build - — 用于安全审查
raccoon-audit
织网后:
- — 用于认证测试
beaver-build - — 用于登录UI的可访问性审查
deer-sense
一张编织精良的蛛网能捕获入侵者,同时让友方通行。 🕷️