skills/modern-auth-2026/SKILL.md
Modern authentication implementation for 2026 - passkeys (WebAuthn), OAuth (Google, Apple), magic links, and cross-device sync. Use for passwordless-first authentication, social login setup, Supabase Auth, Next.js auth flows, and multi-factor authentication. Activate on "passkeys", "WebAuthn", "Google Sign-In", "Apple Sign-In", "magic link", "passwordless", "authentication", "login", "OAuth", "social login". NOT for session management without auth (use standard JWT docs), authorization/RBAC (use security-auditor), or API key management (use api-architect).
npx skillsauth add curiositech/windags-skills modern-auth-2026Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Master passwordless-first authentication with passkeys, OAuth, magic links, and cross-device sync for modern web and mobile applications.
User Auth Request
├── First-time user?
│ ├── YES → Single device?
│ │ ├── YES → Use platform passkey
│ │ └── NO → Use platform passkey + prompt backup registration
│ └── NO → Check existing credentials
│ ├── Has passkeys → Use passkey authentication
│ ├── Has OAuth only → Offer OAuth + passkey upgrade
│ └── Has legacy password → Force migration to passkey
│
├── Device capability check?
│ ├── Platform authenticator available (Face ID/Touch ID/Windows Hello)
│ │ → Prioritize platform passkey
│ ├── Only browser WebAuthn support
│ │ → Use roaming authenticator (YubiKey) OR fallback to OAuth
│ └── No WebAuthn support
│ → Use OAuth (Google/Apple) OR magic link
│
├── Cross-device scenario?
│ ├── User on desktop, has mobile with passkey
│ │ → Show QR code for hybrid transport
│ ├── User lost device access
│ │ → Email recovery → backup passkey registration
│ └── Corporate/shared device
│ → Magic link (no credential storage)
│
└── Compliance requirements?
├── App Store submission with third-party login
│ → MUST include Apple Sign-In
├── GDPR/CCPA region
│ → Default to privacy-first (Apple, passkeys)
└── Enterprise SSO
→ OAuth with organizational domain validation
| User Context | Primary Method | Fallback | Recovery Setup | |-------------|----------------|----------|----------------| | Single iPhone user | Face ID passkey | Apple Sign-In | Email + backup device prompt | | Multi-device Apple user | Platform passkey | iCloud sync | Secondary device registration | | Android user | Fingerprint passkey | Google Sign-In | Google Password Manager sync | | Desktop-only user | Windows Hello OR YubiKey | Magic link | Email + mobile app download | | Privacy-conscious | Platform passkey | Apple Sign-In | Offline backup codes | | Corporate user | SSO OAuth | Hardware key | Admin recovery contact |
Symptom: User gets "Invalid signature" error despite correct biometric Detection Rule: If authentication fails with counter mismatch error in logs Root Cause: Multiple devices using same synced passkey without counter sync Fix: Reset counter to max value from all devices OR implement counter-less verification
// WRONG: Strict counter validation
if (storedCounter >= newCounter) reject();
// RIGHT: Allow counter drift for synced credentials
if (credential.backed_up && Math.abs(storedCounter - newCounter) > 100) {
// Reset to new counter, log security event
}
Symptom: User clicks OAuth button, gets redirected back to login repeatedly Detection Rule: If redirect_uri in OAuth error OR user reports "spinning" login Root Cause: Redirect URI mismatch between provider console and Supabase config Fix: Verify exact URL match including trailing slashes and http/https
// Provider Console: https://yourproject.supabase.co/auth/v1/callback
// Supabase Config: Must match exactly - no trailing slash variations
Symptom: User completes biometric prompt but passkey doesn't save Detection Rule: If registration returns success but no database entry created Root Cause: Challenge verification passed but database insert failed (RLS policy) Fix: Check RLS policies allow insert, verify user context in registration flow
-- WRONG: Restrictive policy
CREATE POLICY "Only verified users" ON passkey_credentials
FOR INSERT WITH CHECK (auth.email_confirmed_at IS NOT NULL);
-- RIGHT: Allow during registration
CREATE POLICY "Users can register" ON passkey_credentials
FOR INSERT WITH CHECK (auth.uid() = user_id);
Symptom: Apple Sign-In suddenly returns 400 Invalid Client error Detection Rule: If Apple OAuth fails with client authentication error after working previously Root Cause: Apple .p8 private key expired (6-month limit) Fix: Set calendar reminders, implement monitoring, rotate keys before expiry
// Add monitoring
const keyCreatedAt = new Date('2024-06-01');
const warningPeriod = 5 * 24 * 60 * 60 * 1000; // 5 days
if (Date.now() - keyCreatedAt.getTime() > (6 * 30 * 24 * 60 * 60 * 1000) - warningPeriod) {
console.warn('Apple key expiring soon!');
}
Symptom: User reports "Check your email" but no emails arrive Detection Rule: If sign-up/magic link triggered but email delivery rate drops below 95% Root Cause: SMTP not configured OR emails marked as spam OR wrong email templates Fix: Verify SMTP settings, check deliverability, update DNS records (SPF/DKIM)
// Add delivery confirmation
await supabase.auth.signInWithOtp({
email,
options: {
shouldCreateUser: false, // Prevent silent failures
}
});
// Check Supabase logs for email delivery status
Scenario: Implementing passkey auth for a Next.js SaaS app with Supabase
Step 1 - Decision Point Navigation:
Step 2 - Database Setup (What novices miss: RLS policies):
-- Experts check: Proper indexing for performance
CREATE TABLE passkey_credentials (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
credential_id text UNIQUE NOT NULL, -- Base64URL encoded
public_key bytea NOT NULL,
counter bigint DEFAULT 0, -- BIGINT not INT (prevents overflow)
transports text[],
backed_up boolean DEFAULT false,
created_at timestamptz DEFAULT now()
);
-- Novice mistake: Forgetting compound index
CREATE INDEX idx_passkey_user_cred ON passkey_credentials(user_id, credential_id);
Step 3 - Registration API (What experts catch: Challenge storage):
// Novice approach: Store challenge in memory (fails with multiple servers)
let challenges = new Map();
// Expert approach: Persistent challenge storage with expiry
await supabase.from('auth_challenges').upsert({
user_id: user.id,
challenge: options.challenge,
type: 'passkey_register',
expires_at: new Date(Date.now() + 5 * 60 * 1000),
});
Step 4 - Frontend Implementation (What novices miss: Error handling):
// Novice: Generic error handling
try {
const credential = await startRegistration(options);
} catch (error) {
setError('Registration failed');
}
// Expert: Specific error cases
try {
const credential = await startRegistration(options);
} catch (error) {
if (error.name === 'NotAllowedError') {
setError('Registration cancelled or timeout');
} else if (error.name === 'InvalidStateError') {
setError('Device already registered for this account');
} else if (error.name === 'NotSupportedError') {
// Fallback to OAuth
redirectToAppleSignIn();
} else {
setError(`Registration failed: ${error.message}`);
}
}
Step 5 - Testing (Expert checks novices skip):
Do NOT use this skill for:
security-auditor skill for RBAC, permissions, access controlapi-architect skill for service-to-service authenticationsupabase-admin skill for database-level security rulescompliance-auditor skill for GDPR/HIPAA/SOC2 requirementsperformance-optimizer skill for auth-related bottlenecksDelegate to other skills when user asks about:
supabase-adminsecurity-auditorapi-architectcompliance-auditortools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.