| name | moai-security-auth |
| version | 4.0.0 |
| status | stable |
| description | Enterprise Skill for advanced development |
| allowed-tools | Read, Bash, WebSearch, WebFetch |
moai-security-auth: Modern Authentication Patterns
Advanced Authentication with MFA, FIDO2, WebAuthn & Passkeys
Trust Score: 9.8/10 | Version: 4.0.0 | Enterprise Mode | Last Updated: 2025-11-12
Overview
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:
- Implementing secure session management
- Adding multi-factor authentication (MFA)
- Migrating to passwordless authentication (Passkeys/WebAuthn)
- Building FIDO2 hardware key support
- Setting up OAuth 2.1 provider integration
- Implementing JWT session refresh logic
- Building secure single sign-on (SSO) systems
- Managing authentication state in distributed systems
Level 1: Foundations (FREE TIER)
What is Modern Authentication?
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 & WebAuthn Fundamentals
FIDO2 Standard (2018):
- Published by FIDO Alliance (Google, Microsoft, Apple, etc.)
- Two-factor authentication using hardware keys or biometrics
- No password transmission needed
WebAuthn (W3C Standard):
- Browser API for FIDO2 authentication
- Supports USB keys, biometrics, platform authenticators
- Cryptographic proof instead of password verification
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--------------|
Session Management (NextAuth.js 5.x)
NextAuth.js 5.0.0 (November 2025):
- Complete rewrite for Next.js 15
- JWT sessions by default (stateless)
- Built-in OAuth/OIDC provider support
- Passwordless email magic links
Key Concepts:
-
Callbacks (Control authentication flow)
authorized: Check if user has access
signIn: Validate credentials before session creation
jwt: Modify JWT payload
session: Customize session object
-
Providers (Authentication sources)
- OAuth (Google, GitHub, Microsoft)
- Credentials (username/password)
- Email magic link
- FIDO2/WebAuthn
-
Events (Logging & audit trail)
signIn: User logged in
signOut: User logged out
createUser: New user registered
updateUser: User updated
Level 2: Intermediate Patterns (STANDARD TIER)
NextAuth.js 5.x Implementation
Basic Setup (JWT Sessions):
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: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
allowDangerousEmailAccountLinking: false
}),
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);
FIDO2/WebAuthn Implementation
Registration (User Setup):
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers/iso';
export async function startRegistration(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,
authenticatorSelection: {
authenticatorAttachment: 'cross-platform',
residentKey: 'preferred',
userVerification: 'preferred'
},
attestationType: 'direct',
: [-, -]
});
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) {
const user = await db.users.findByEmail(email);
if (!user) throw new Error('User not found');
const credentials = await db.webauthnCredentials.findByUserId(user.id);
const options = generateAuthenticationOptions({
rpID: process.env.WEBAUTHN_RP_ID,
allowCredentials: credentials.map(cred => ({
id: cred.credential_id,
transports: cred.transports
})),
userVerification: 'required'
});
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;
}
Multi-Factor Authentication (TOTP)
Time-based One-Time Password:
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
export async function setupTOTP(user: User) {
const secret = authenticator.generateSecret({
name: `My App (${user.email})`
});
const qrCode = await QRCode.toDataURL(secret);
await redis.setex(
`totp:pending:${user.id}`,
600,
secret
);
return { secret, qrCode };
}
export async function verifyTOTPSetup(user: User, token: string) {
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 with Custom Strategy
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';
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'
});
}
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.);
}
);
Level 3: Enterprise Patterns (PREMIUM TIER)
Passkeys (Passwordless Future)
Passkey Registration & Authentication:
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,
authenticatorSelection: {
authenticatorAttachment: 'platform',
residentKey: 'required',
userVerification: 'required'
},
attestationType: 'direct'
});
return options;
}
export async function authenticateWithPasskey(: ) {
user = db..(email);
credentials = db..(
user.,
{ : }
);
options = ({
: process..,
: []
});
options;
}
Session Refresh & Sliding Windows
NextAuth.js JWT Refresh:
async function refreshAccessToken(token: JWT) {
try {
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;
};
Audit Logging & Suspicious Activity
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);
const lastLogin = await db.authLogs
.findLastByUser(userId)
.select('geoLocation', 'timestamp');
const isSuspicious = lastLogin && (
lastLogin.geoLocation.country !== geoLocation.country &&
Date.now() - lastLogin.timestamp < 3600000
);
await db.authLogs.create({
user_id: userId,
event,
ip,
userAgent,
geoLocation,
suspicious: isSuspicious,
timestamp: new Date()
});
if (isSuspicious) {
await (userId, {
: ,
: {
: geoLocation.,
: ().()
}
});
}
}
() {
originalSend = res.;
res. = () {
(req..()) {
(req.?., req., {
: req.,
: res.,
: req.,
: req.[]
}).(.);
}
originalSend.(, data);
};
();
}
Reference
Official Documentation
Tools & Libraries (November 2025 Versions)
- next-auth: 5.0.x
- passport: 0.7.x
- @simplewebauthn/server: 10.0.x
- otplib: 12.x
- bcryptjs: 2.4.x
- jsonwebtoken: 9.x
- redis: 5.x
Common Vulnerabilities & Mitigations
| 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