用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/AJBcoding/claude-skill-eval --skill moai-security-auth命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | moai-security-auth |
| version | 4.0.0 |
| status | stable |
| description | Enterprise Skill for advanced development |
| allowed-tools | Read, Bash, WebSearch, WebFetch |
Advanced Authentication with MFA, FIDO2, WebAuthn & Passkeys
Trust Score: 9.8/10 | Version: 4.0.0 | Enterprise Mode | Last Updated: 2025-11-12
Authentication is the foundation of application security. Modern patterns have evolved from passwords to passwordless authentication using FIDO2, WebAuthn, and Passkeys. This Skill covers current best practices for NextAuth.js 5.x, Passport.js, and FIDO2 implementations.
When to use this Skill:
Legacy Flow (2010s):
User → Username/Password → Database Hash Comparison → Session Token
Modern Flow (2025):
User → Biometric/Hardware → WebAuthn Server → Cryptographic Verification
OR
User → OAuth Provider → Provider Verification → Access Token + ID Token
Authentication Evolution:
| Era | Method | Security | User Experience |
|---|---|---|---|
| 2000-2010 | Password | Weak | Good |
| 2010-2020 | Password + 2FA | Medium | Poor |
| 2020-2025 | Passwordless | Strong | Excellent |
| 2025+ | Passkeys | Strongest | Best |
FIDO2 Standard (2018):
WebAuthn (W3C Standard):
Registration Flow:
User Device Authenticator Relying Party (Server)
| | |
|--Challenge------->| |
| |--User Verification |
| | (Biometric/PIN) |
| | |
|<--Attestation------| |
| | |
|--PublicKey + Attestation------->Verify|Store
Authentication Flow:
User Device Authenticator Relying Party (Server)
| | |
| |<--Challenge------------|
| | |
|--User Verification-| |
| |--Assertion (Signed)-->|Verify Signature
| | |
|<--Authenticated----|<--Success--------------|
NextAuth.js 5.0.0 (November 2025):
Key Concepts:
Callbacks (Control authentication flow)
authorized: Check if user has accesssignIn: Validate credentials before session creationjwt: Modify JWT payloadsession: Customize session objectProviders (Authentication sources)
Events (Logging & audit trail)
signIn: User logged insignOut: User logged outcreateUser: New user registeredupdateUser: User updatedBasic Setup (JWT Sessions):
// lib/auth.ts
import NextAuth, { type NextAuthConfig } from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { encode as defaultEncode, decode as defaultDecode } from 'next-auth/jwt';
const config = {
providers: [
// 1. OAuth Provider (GitHub)
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
allowDangerousEmailAccountLinking: false
}),
// 2. Passwordless Email Magic Link
Email({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: parseInt(process.env.EMAIL_SERVER_PORT),
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
: process..
}),
({
: {
: { : , : },
: { : , : },
: { : , : , : }
},
() {
user = db..(credentials.);
(!user) ;
passwordMatch = bcrypt.(
credentials.,
user.
);
(!passwordMatch) ;
(user.) {
(!credentials.) {
();
}
mfaValid = (
credentials.,
user.
);
(!mfaValid) {
();
}
}
{
: user.,
: user.,
: user.
};
}
})
],
: {
: (params) => {
(params.?. === ) {
({ ...params, : .() / + });
}
(params);
},
: defaultDecode
},
: {
: ,
: * * * ,
: * *
},
: {
() {
isLoggedIn = !!auth?.;
isOnAdminPage = request...();
(isOnAdminPage) {
isLoggedIn && auth.. === ;
}
;
},
() {
(!user.) {
();
}
(user.) {
();
}
;
},
() {
(user) {
token. = user.;
token. = user.;
}
(account?.) {
token. = account.;
token. = account. * ;
}
(token. &&
.() < token.) {
token;
}
(token);
},
() {
session.. = token.;
session.. = token.;
session. = token.;
session;
}
},
: {
: ,
: ,
:
}
} ;
{ handlers, auth, signIn, signOut } = (config);
Registration (User Setup):
// lib/webauthn.ts
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers/iso';
export async function startRegistration(user: User) {
// 1. Generate challenge
const options = generateRegistrationOptions({
rpID: process.env.WEBAUTHN_RP_ID, // domain.com
rpName: 'My Application',
userID: isoBase64URL.fromBuffer(Buffer.from(user.id)),
userName: user.email,
userDisplayName: user.name,
// 2. Require user verification (biometric/PIN)
authenticatorSelection: {
authenticatorAttachment: 'cross-platform', // USB key
residentKey: 'preferred', // Passkey support
userVerification: 'preferred' // Biometric
},
// 3. Attestation for registration verification
attestationType: 'direct',
// 4. Support multiple algorithms
: [-, -]
});
redis.(
,
,
.(options.)
);
options;
}
() {
challengeStr = redis.();
challenge = .(challengeStr);
verification = ({
: attestationResponse,
: challenge,
: process..,
: process..,
: ,
:
});
(!verification.) {
();
}
db..({
: user.,
: isoBase64URL.(
verification..
),
: verification..,
: verification..,
: attestationResponse..(),
: ()
});
redis.();
verification;
}
Authentication (Sign In):
import { generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server';
export async function startAuthentication(email: string) {
// 1. Get user's credentials
const user = await db.users.findByEmail(email);
if (!user) throw new Error('User not found');
const credentials = await db.webauthnCredentials.findByUserId(user.id);
// 2. Generate challenge
const options = generateAuthenticationOptions({
rpID: process.env.WEBAUTHN_RP_ID,
// User must verify with same credential
allowCredentials: credentials.map(cred => ({
id: cred.credential_id,
transports: cred.transports
})),
userVerification: 'required' // Must verify identity
});
// 3. Store challenge for verification
await redis.setex(
`webauthn:auth:${user.id}`,
,
.(options.)
);
options;
}
() {
user = db..(email);
credentialID = assertionResponse.;
credential = db..(
.(credentialID, )
);
challengeStr = redis.();
challenge = .(challengeStr);
verification = ({
: assertionResponse,
: challenge,
: process..,
: process..,
: {
: credential.,
: credential.,
: credential.
}
});
(!verification.) {
();
}
db..(credential., {
: verification..
});
user;
}
Time-based One-Time Password:
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
export async function setupTOTP(user: User) {
// 1. Generate secret
const secret = authenticator.generateSecret({
name: `My App (${user.email})`
});
// 2. Generate QR code
const qrCode = await QRCode.toDataURL(secret);
// 3. Store temporary (not yet verified)
await redis.setex(
`totp:pending:${user.id}`,
600, // 10 minutes
secret
);
return { secret, qrCode };
}
export async function verifyTOTPSetup(user: User, token: string) {
// 1. Get pending secret
const secret = await redis.get(`totp:pending:${user.id}`);
if (!secret) throw new Error();
isValid = authenticator.(token, secret);
(!isValid) ();
backupCodes = .({ : }).(
crypto.().().()
);
db..(user., {
: ,
: secret,
: backupCodes.(
bcrypt.(code, )
)
});
redis.();
backupCodes;
}
() {
isBackup = user..(
bcrypt.(token, hashedCode)
);
(isBackup) {
codes = user..(
!bcrypt.(token, code)
);
db..(user., { : codes });
;
}
isValid = authenticator.(token, user.);
isValid;
}
totpAttempts = <, >();
() {
key = ;
attempts = totpAttempts.(key) || ;
(attempts >= ) {
();
}
isValid = (user, token);
(!isValid) {
totpAttempts.(key, attempts + );
( totpAttempts.(key), );
();
}
totpAttempts.(key);
;
}
Passport.js 0.7.x:
import { Strategy as LocalStrategy } from 'passport-local';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import bcrypt from 'bcryptjs';
import passport from 'passport';
// 1. Local Strategy (username/password)
passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await db.users.findByEmail(email);
if (!user) {
return done(null, false, {
message: 'Invalid credentials'
});
}
// Check if account is locked
if (user.loginAttempts >= 5 &&
Date.now() < user.lockUntil) {
return (, , {
:
});
}
isPasswordValid = bcrypt.(
password,
user.
);
(!isPasswordValid) {
db..(user., {
: (user. || ) + ,
: user. >=
? (.() + * )
:
});
(, , {
:
});
}
(user. > ) {
db..(user., {
: ,
:
});
}
(, user);
} (err) {
(err);
}
}
));
passport.( (
{
: .(),
: process..,
: []
},
(jwtPayload, done) => {
{
user = db..(jwtPayload.);
(!user) {
(, );
}
isBlacklisted = redis.(
);
(isBlacklisted) {
(, );
}
(, user, jwtPayload);
} (err) {
(err);
}
}
));
passport.( {
(, user.);
});
passport.( (id, done) => {
{
user = db..(id);
(, user);
} (err) {
(err);
}
});
app.(,
passport.(, { : }),
{
token = jwt.(
{ : req.., : () },
process..,
{ : }
);
res.({ token, : req. });
}
);
app.(,
passport.(, { : }),
{
res.(req.);
}
);
Passkey Registration & Authentication:
// Passkeys = WebAuthn + Backup Sync (iCloud, Google Password Manager)
// Registration same as WebAuthn, but with different UX
export async function registerPasskey(user: User) {
const options = generateRegistrationOptions({
rpID: process.env.WEBAUTHN_RP_ID,
rpName: 'My Application',
userID: isoBase64URL.fromBuffer(Buffer.from(user.id)),
userName: user.email,
userDisplayName: user.name,
// Passkey-specific settings
authenticatorSelection: {
authenticatorAttachment: 'platform', // Device built-in (not USB)
residentKey: 'required', // Passkey must be resident
userVerification: 'required' // Biometric/PIN required
},
attestationType: 'direct'
});
// Passkey will be synced by platform (iCloud, Google, etc.)
return options;
}
// Authenticate with any passkey (phone, laptop, shared device)
export async function authenticateWithPasskey(: ) {
user = db..(email);
credentials = db..(
user.,
{ : }
);
options = ({
: process..,
: []
});
options;
}
NextAuth.js JWT Refresh:
async function refreshAccessToken(token: JWT) {
try {
// Refresh token with OAuth provider
const response = await fetch(
`https://oauth-provider.com/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID,
client_secret: process.env.OAUTH_CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: token.refreshToken
})
}
);
const refreshedTokens = await response.json();
if (!response.ok) throw refreshedTokens;
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken
};
} catch (error) {
{ ...token, : };
}
}
= () => {
(trigger === && session?.) {
token. = session.;
}
(token. && .() < token. * - * * * ) {
token. = .() / + * * * ;
}
token;
};
Authentication Event Logging:
export async function logAuthEvent(
userId: string,
event: string,
metadata: Record<string, any>
) {
const ip = metadata.ip;
const userAgent = metadata.userAgent;
const geoLocation = await getGeolocation(ip);
// Check for suspicious activity
const lastLogin = await db.authLogs
.findLastByUser(userId)
.select('geoLocation', 'timestamp');
const isSuspicious = lastLogin && (
// Login from new country in short time
lastLogin.geoLocation.country !== geoLocation.country &&
Date.now() - lastLogin.timestamp < 3600000 // 1 hour
);
await db.authLogs.create({
user_id: userId,
event,
ip,
userAgent,
geoLocation,
suspicious: isSuspicious,
timestamp: new Date()
});
// Alert user if suspicious
if (isSuspicious) {
await (userId, {
: ,
: {
: geoLocation.,
: ().()
}
});
}
}
() {
originalSend = res.;
res. = () {
(req..()) {
(req.?., req., {
: req.,
: res.,
: req.,
: req.[]
}).(.);
}
originalSend.(, data);
};
();
}
| Vulnerability | OWASP | Mitigation |
|---|---|---|
| Weak Password | A02:2021 | TOTP/WebAuthn instead |
| Session Fixation | A02:2021 | Rotate session ID on login |
| Brute Force | A07:2021 | Rate limit + account lockout |
| Token Exposure | A02:2021 | Store in httpOnly cookie |
| Credential Stuffing | A02:2021 | Use bcrypt + salting |
| MFA Bypass | A07:2021 | Enforce MFA verification |
Version: 4.0.0 Enterprise Skill Category: Security (Authentication & Authorization) Complexity: Medium-Advanced Time to Implement: 3-5 hours per component Prerequisites: Node.js, React/Vue.js, OAuth concepts, WebAuthn API