算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
frontend-authentication【设计与创意】Use this skill when the user says 'auth', 'authentication', 'login', 'signup', 'JWT', 'OAuth', 'OAuth2', 'OpenID Connect', 'route guard', 'protected route', 'auth middleware', 'token storage', 'refresh token', 'session management', 'access token', 'logout', 'SSO', 'magic link', 'passwordless', 'MFA', '2FA'. This skill enforces secure token storage, p…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Implement secure authentication on the frontend: token acquisition, storage, route protection, session refresh, and logout — compatible with any auth provider. Tokens are stored securely. Routes are guarded by middleware or wrapper components. OAuth flows follow PKCE best practices.
Exact phrases: "auth", "authentication", "login", "signup", "JWT", "OAuth", "route guard", "protected route", "token storage", "refresh token", "session", "access token", "logout", "SSO", "magic link", "MFA".
Complete auth integration: provider config, auth hook/context, protected route component, token management, login/logout flows.
## Strategy
<auth-provider, token-storage, route-guard-pattern>
## Setup
<provider-config, auth-context>
## Implementation
<login, logout, token-refresh, protected-routes>
## Session
<persistence, recovery, timeout>
—
Compression footer: frontend-auth/v1 | provider: <provider> | storage: <httpOnly|memory|localStorage> | routes: <count>
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
4096 tokens
Team size and requirements?
|-- Solo / small team, need quick setup -->
| |-- Want hosted auth UI? --> Clerk or Auth0
| |-- Want database integration? --> Supabase Auth
| |-- Need social login quick? --> Firebase Auth
|-- Mid-size team, need control -->
| |-- Using AWS? --> Cognito
| |-- Need enterprise SSO? --> Auth0 or Azure AD B2C
| |-- Want self-hosted? --> Keycloak or Supertokens
|-- Large enterprise -->
|-- Have identity provider? --> OIDC federation
|-- Building custom? --> Custom JWT with OAuth2
Is the app a production SPA with sensitive data?
|-- YES --> Can the API set httpOnly cookies?
| |-- YES --> httpOnly cookie (best security)
| |-- NO --> In-memory + refresh token rotation
|-- NO --> Is it a low-sensitivity app or prototype?
|-- YES --> localStorage (acceptable for low-risk)
|-- NO --> sessionStorage (tab-scoped, moderate security)
Auth token in localStorage?
|-- Risk: XSS → token theft
|-- Mitigation: short expiry, CSP headers, input sanitization
|-- Recommendation: Never for production banking/health/enterprise apps
Route type?
|-- Public (landing, marketing, login) → no guard
|-- Authenticated only (dashboard, profile) → auth guard
|-- Role-restricted (admin panel) → auth guard + role/perm check
|-- Guest-only (login/signup when logged in) → redirect to home
// Auth0
import { Auth0Provider } from '@auth0/auth0-react'
<Auth0Provider domain="dev-xxx.us.auth0.com" clientId="xxx" authorizationParams={{ redirect_uri: window.location.origin }}>
<App />
</Auth0Provider>
// Supabase
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Custom JWT
const login = async (email: string, password: string) => {
const { accessToken, refreshToken } = await api.post('/auth/login', { email, password })
tokenStorage.set({ accessToken, refreshToken })
}
| Storage | Security | Persists Refresh | Use When |
|---|---|---|---|
| httpOnly cookie | Best | Yes | Same-origin API, production apps |
| In-memory | Good | No (must use refresh token) | SPAs with refresh token rotation |
| localStorage with prefix | Moderate | Yes | Low-sensitivity apps, prototyping |
| sessionStorage | Moderate | No (tab-scoped) | Short-lived sessions |
interface AuthState {
user: User | null
accessToken: string | null
isAuthenticated: boolean
isLoading: boolean
}
interface AuthActions {
login: (credentials: Credentials) => Promise<void>
logout: () => Promise<void>
getAccessToken: () => Promise<string | null>
}
// Complete auth context with session recovery
const AuthContext = createContext<AuthState & AuthActions | null>(null)
function AuthProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<AuthState>({
user: null,
accessToken: null,
isAuthenticated: false,
isLoading: true, // start loading — check for existing session
})
// Session recovery on mount
useEffect(() => {
const storedUser = sessionStorage.getItem('auth_user')
const storedToken = sessionStorage.getItem('auth_token')
if (storedUser && storedToken) {
setState({
user: JSON.parse(storedUser),
accessToken: storedToken,
isAuthenticated: true,
isLoading: false,
})
} else {
setState(s => ({ ...s, isLoading: false }))
}
}, [])
const getAccessToken = useCallback(async () => {
try {
const newToken = await refreshAccessToken()
return newToken
} catch {
logout()
return null
}
}, [])
const login = useCallback(async (credentials: Credentials) => {
const { accessToken, refreshToken, user } = await api.post('/auth/login', credentials)
tokenStorage.setRefreshToken(refreshToken)
setState({ user, accessToken, isAuthenticated: true, isLoading: false })
}, [])
const logout = useCallback(async () => {
try { await api.post('/auth/logout') } catch { /* ignore */ }
tokenStorage.clear()
setState({ user: null, accessToken: null, isAuthenticated: false, isLoading: false })
}, [])
return (
<AuthContext.Provider value={{ ...state, login, logout, getAccessToken }}>
{children}
</AuthContext.Provider>
)
}
function useAuth() {
const ctx = useContext(AuthContext)
if (!ctx) throw new Error('useAuth must be inside AuthProvider')
return ctx
}
// React component guard
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth()
if (isLoading) return <Spinner />
if (!isAuthenticated) return <Navigate to="/login" state={{ from: location }} replace />
return <>{children}</>
}
// React Router loader guard
const protectedLoader = async () => {
const auth = getAuth()
if (!auth.isAuthenticated) throw redirect('/login')
return null
}
// Role-based guard
function AdminRoute({ children }: { children: ReactNode }) {
const { user, isAuthenticated, isLoading } = useAuth()
if (isLoading) return <Spinner />
if (!isAuthenticated) return <Navigate to="/login" replace />
if (user?.role !== 'admin') return <Navigate to="/forbidden" replace />
return <>{children}</>
}
// Axios interceptor for automatic token refresh
let isRefreshing = false
let failedQueue: Array<{ resolve: Function; reject: Function }> = []
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach(prom => {
if (token) prom.resolve(token)
else prom.reject(error)
})
failedQueue = []
}
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
// Only retry 401s that haven't been retried
if (error.response?.status !== 401 || originalRequest._retry) {
return Promise.reject(error)
}
// If already refreshing, queue this request
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
}).then(token => {
originalRequest.headers.Authorization = `Bearer ${token}`
return api(originalRequest)
})
}
originalRequest._retry = true
isRefreshing = true
try {
const { accessToken } = await refreshAccessToken()
processQueue(null, accessToken)
originalRequest.headers.Authorization = `Bearer ${accessToken}`
return api(originalRequest)
} catch (refreshError) {
processQueue(refreshError, null)
logout() // force re-login
return Promise.reject(refreshError)
} finally {
isRefreshing = false
}
}
)
async function logout() {
await api.post('/auth/logout') // invalidate refresh token server-side
tokenStorage.clear()
authContext.reset()
router.navigate('/login')
}
// Generate code verifier and challenge
const verifier = generateRandomString(64)
const challenge = await sha256(verifier)
localStorage.setItem('code_verifier', verifier)
// Redirect to auth server
window.location.href = `https://auth.example.com/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&code_challenge=${challenge}&code_challenge_method=S256&state=${state}`
// On callback, exchange code for tokens
const { code, state: returnedState } = parseCallbackUrl()
const verifier = localStorage.getItem('code_verifier')
const tokens = await api.post('/auth/token', { code, verifier, redirect_uri: REDIRECT_URI })
// Proactive token refresh — refresh before expiry, not after
function useTokenRefresh(expiresIn: number, threshold = 60_000) {
const { getAccessToken, isAuthenticated } = useAuth()
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout>>()
useEffect(() => {
if (!isAuthenticated) return
// Refresh 60s before expiry
const refreshDelay = Math.max(0, expiresIn - threshold)
refreshTimeoutRef.current = setTimeout(async () => {
await getAccessToken()
}, refreshDelay)
return () => clearTimeout(refreshTimeoutRef.current)
}, [isAuthenticated, expiresIn, getAccessToken])
}
// Concurrent tab session management
function useSessionSync() {
useEffect(() => {
// Listen for logout events from other tabs
const handleStorage = (e: StorageEvent) => {
if (e.key === 'auth_logout') {
window.location.href = '/login'
}
}
window.addEventListener('storage', handleStorage)
return () => window.removeEventListener('storage', handleStorage)
}, [])
}
type MfaStep = 'verify' | 'enroll' | 'challenge'
type MfaMethod = 'totp' | 'sms' | 'email' | 'recovery-code'
interface MfaState {
required: boolean
step: MfaStep
method: MfaMethod
verified: boolean
}
function useMfa() {
const [mfa, setMfa] = useState<MfaState>({
required: false,
step: 'verify',
method: 'totp',
verified: false,
})
const verifyCode = async (code: string) => {
await api.post('/auth/mfa/verify', { code, method: mfa.method })
setMfa(prev => ({ ...prev, verified: true }))
}
const enrollTotp = async () => {
const { secret, qrCode } = await api.post('/auth/mfa/enroll', { method: 'totp' })
return { secret, qrCode }
}
return { mfa, verifyCode, enrollTotp }
}
// Login
const login = async (email: string, password: string) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (!response.ok) throw new ApiError('Invalid credentials', response.status)
// Server sets httpOnly cookie for access token
// Returns refresh token (short-lived, client-side)
const { refreshToken, user } = await response.json()
sessionStorage.setItem('refreshToken', refreshToken)
return user
}
import { useAuth0 } from '@auth0/auth0-react'
function Profile() {
const { user, isAuthenticated, isLoading, loginWithRedirect, logout, getAccessTokenSilently } = useAuth0()
if (isLoading) return <Spinner />
if (!isAuthenticated) return <button onClick={() => loginWithRedirect()}>Log in</button>
return <div>Welcome, {user.name}</div>
}
import { createClient, User } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
function useSupabaseAuth() {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
})
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null)
})
return () => subscription.unsubscribe()
}, [])
const loginWithGoogle = () => supabase.auth.signInWithOAuth({ provider: 'google' })
const loginWithEmail = (email: string, password: string) =>
supabase.auth.signInWithPassword({ email, password })
const logout = () => supabase.auth.signOut()
return { user, loginWithGoogle, loginWithEmail, logout }
}
| Provider | Auth Methods | Social Login | MFA | SSO | Pricing |
|---|---|---|---|---|---|
| Auth0 | OIDC, OAuth2, SAML | 50+ providers | Yes | Yes | Free tier: 7K users |
| Clerk | OIDC, OAuth2 | 10+ providers | Yes | Yes | Free tier: 5K users |
| Supabase | OAuth2, email/pw | 10+ providers | Yes | No | Free tier: 50K users |
| Firebase | OAuth2, email/pw, phone | 10+ providers | Yes | No | Free tier: 10K auth |
| Cognito | OIDC, SAML, OAuth2 | Social + SAML | Yes | Yes | Per MAU pricing |
| Custom JWT | Email/password | Manual | Manual | Manual | Infrastructure cost |
No artifact produced unless requested.
Next skill: frontend-security — CSP headers, XSS prevention for auth pages.
Carry forward: auth provider, token storage strategy, route guard pattern, refresh strategy.