Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    jg-chalk-io

    moai-security-auth

    jg-chalk-io/moai-security-auth
    Security
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Modern authentication patterns with MFA, FIDO2, WebAuthn & Passkeys

    SKILL.md

    moai-security-auth: Modern Authentication Patterns

    Advanced authentication with MFA, FIDO2, WebAuthn & Passkeys

    Trust Score: 9.8/10 | Version: 4.0.0


    Overview

    Enterprise authentication expert covering modern security patterns:

    • Passwordless Authentication: FIDO2, WebAuthn, and Passkeys
    • Multi-Factor Authentication: TOTP, SMS, and hardware tokens
    • OAuth 2.1 Integration: Social login and enterprise SSO
    • Session Management: JWT, refresh tokens, and secure cookies
    • Advanced Security: Rate limiting, account lockout, and audit logging

    Core Technologies:

    • NextAuth.js 5.x for Next.js applications
    • Passport.js for Express.js applications
    • WebAuthn API for passwordless authentication
    • JWT for stateless session management

    Authentication Evolution

    Era Method Security UX
    2000-2010 Password Weak Good
    2010-2020 Password + 2FA Medium Poor
    2020-2025 Passwordless Strong Excellent
    2025+ Passkeys Strongest Best

    NextAuth.js 5.x Implementation

    Complete Configuration

    // lib/auth.ts
    import NextAuth from "next-auth";
    import GitHub from "next-auth/providers/github";
    import Credentials from "next-auth/providers/credentials";
    import { DrizzleAdapter } from "@auth/drizzle-adapter";
    
    export const config = {
      adapter: DrizzleAdapter(db),
      providers: [
        GitHub({
          clientId: process.env.GITHUB_CLIENT_ID!,
          clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        }),
    
        Credentials({
          credentials: {
            email: { label: "Email", type: "email" },
            password: { label: "Password", type: "password" },
            mfaCode: { label: "2FA Code", type: "text", optional: true },
          },
          async authorize(credentials) {
            const user = await db.query.users.findFirst({
              where: eq(users.email, credentials.email),
            });
    
            if (!user) return null;
    
            // Verify password
            const valid = await bcrypt.compare(
              credentials.password,
              user.passwordHash
            );
    
            if (!valid) return null;
    
            // Verify MFA if enabled
            if (user.mfaEnabled && user.mfaSecret) {
              if (!credentials.mfaCode) {
                throw new Error("MFA code required");
              }
    
              const mfaValid = speakeasy.totp.verify({
                secret: user.mfaSecret,
                encoding: "base32",
                token: credentials.mfaCode,
              });
    
              if (!mfaValid) throw new Error("Invalid MFA code");
            }
    
            return {
              id: user.id,
              email: user.email,
              name: user.name,
              role: user.role,
            };
          },
        }),
      ],
    
      session: {
        strategy: "jwt",
        maxAge: 30 * 24 * 60 * 60, // 30 days
      },
    
      callbacks: {
        async authorized({ auth, request }) {
          const isLoggedIn = !!auth?.user;
          const isAdmin = auth?.user?.role === "admin";
          const isAdminRoute = request.nextUrl.pathname.startsWith("/admin");
    
          return isAdminRoute ? isLoggedIn && isAdmin : isLoggedIn;
        },
    
        async jwt({ token, user }) {
          if (user) {
            token.id = user.id;
            token.role = user.role;
          }
          return token;
        },
    
        async session({ session, token }) {
          if (token) {
            session.user.id = token.id;
            session.user.role = token.role;
          }
          return session;
        },
      },
    
      pages: {
        signIn: "/auth/signin",
        error: "/auth/error",
      },
    };
    
    export const { handlers, auth, signIn, signOut } = NextAuth(config);
    

    MFA Implementation

    TOTP Setup

    // lib/mfa.ts
    import speakeasy from "speakeasy";
    import QRCode from "qrcode";
    
    export class MFAService {
      static generateSecret(userEmail: string) {
        return speakeasy.generateSecret({
          name: `MyApp (${userEmail})`,
          issuer: "MyApp",
        });
      }
    
      static async generateQRCode(secret: speakeasy.GeneratedSecret) {
        const otpauthUrl = speakeasy.otpauthURL({
          secret: secret.base32,
          label: secret.name,
          issuer: secret.issuer,
        });
    
        return await QRCode.toDataURL(otpauthUrl);
      }
    
      static verifyToken(secret: string, token: string): boolean {
        return speakeasy.totp.verify({
          secret,
          encoding: "base32",
          token,
          window: 1, // Allow time drift
        });
      }
    
      static async enableMFA(userId: string, secret: string, token: string) {
        if (!this.verifyToken(secret, token)) {
          throw new Error("Invalid verification code");
        }
    
        await db
          .update(users)
          .set({
            mfaEnabled: true,
            mfaSecret: secret,
            mfaEnabledAt: new Date(),
          })
          .where(eq(users.id, userId));
      }
    }
    

    MFA API Routes

    // app/api/auth/mfa/enable/route.ts
    import { auth } from "@/lib/auth";
    import { MFAService } from "@/lib/mfa";
    
    export async function POST(request: Request) {
      const session = await auth();
      if (!session?.user?.id) {
        return Response.json({ error: "Unauthorized" }, { status: 401 });
      }
    
      const { token } = await request.json();
      const secret = MFAService.generateSecret(session.user.email);
    
      await MFAService.enableMFA(session.user.id, secret.base32, token);
    
      return Response.json({ success: true });
    }
    

    WebAuthn & Passkeys

    WebAuthn Service

    // lib/webauthn.ts
    import {
      generateRegistrationOptions,
      verifyRegistrationResponse,
      generateAuthenticationOptions,
      verifyAuthenticationResponse,
    } from "@simplewebauthn/server";
    
    const rpID = process.env.WEBAUTHN_RP_ID!;
    const rpName = "MyApp";
    const origin = process.env.WEBAUTHN_ORIGIN!;
    
    export class WebAuthnService {
      static async startRegistration(userId: string, email: string, name: string) {
        const options = generateRegistrationOptions({
          rpID,
          rpName,
          userID: new TextEncoder().encode(userId),
          userName: email,
          userDisplayName: name,
          authenticatorSelection: {
            residentKey: "preferred", // Passkey support
            userVerification: "preferred", // Biometric
          },
        });
    
        // Store challenge in session
        await redis.setex(
          `webauthn:register:${userId}`,
          900, // 15 minutes
          JSON.stringify(options.challenge)
        );
    
        return options;
      }
    
      static async completeRegistration(userId: string, response: any) {
        const challengeStr = await redis.get(`webauthn:register:${userId}`);
        if (!challengeStr) {
          throw new Error("Registration challenge expired");
        }
    
        const expectedChallenge = JSON.parse(challengeStr);
        const verification = await verifyRegistrationResponse({
          response,
          expectedChallenge,
          expectedOrigin: origin,
          expectedRPID: rpID,
          requireUserVerification: true,
        });
    
        if (!verification.verified || !verification.registrationInfo) {
          throw new Error("Registration verification failed");
        }
    
        // Store credential
        await db.webauthnCredentials.create({
          userId,
          credentialId: verification.registrationInfo.credentialID,
          credentialPublicKey: verification.registrationInfo.credentialPublicKey,
          counter: verification.registrationInfo.counter,
          transports: response.response.transports,
        });
    
        await redis.del(`webauthn:register:${userId}`);
    
        return verification;
      }
    
      static async startAuthentication(email: string) {
        const user = await db.query.users.findFirst({
          where: eq(users.email, email),
          with: { credentials: true },
        });
    
        if (!user) throw new Error("User not found");
    
        const options = generateAuthenticationOptions({
          rpID,
          allowCredentials: user.credentials.map((cred) => ({
            id: cred.credentialId,
            type: "public-key",
            transports: cred.transports,
          })),
          userVerification: "preferred",
        });
    
        await redis.setex(
          `webauthn:auth:${user.id}`,
          900,
          JSON.stringify(options.challenge)
        );
    
        return options;
      }
    
      static async completeAuthentication(email: string, response: any) {
        const user = await db.query.users.findFirst({
          where: eq(users.email, email),
          with: { credentials: true },
        });
    
        if (!user) throw new Error("User not found");
    
        const challengeStr = await redis.get(`webauthn:auth:${user.id}`);
        if (!challengeStr) {
          throw new Error("Authentication challenge expired");
        }
    
        const expectedChallenge = JSON.parse(challengeStr);
        const credential = user.credentials.find(
          (cred) => Buffer.compare(cred.credentialId, response.id) === 0
        );
    
        if (!credential) throw new Error("Credential not found");
    
        const verification = await verifyAuthenticationResponse({
          response,
          expectedChallenge,
          expectedOrigin: origin,
          expectedRPID: rpID,
          credential: {
            id: credential.credentialId,
            publicKey: credential.credentialPublicKey,
            counter: credential.counter,
            transports: credential.transports,
          },
          requireUserVerification: true,
        });
    
        if (!verification.verified) {
          throw new Error("Authentication verification failed");
        }
    
        // Update counter
        await db.webauthnCredentials
          .update({
            counter: verification.authenticationInfo.newCounter,
          })
          .where(eq(webauthnCredentials.id, credential.id));
    
        await redis.del(`webauthn:auth:${user.id}`);
    
        return user;
      }
    }
    

    Security Middleware

    Rate Limiting

    // middleware.ts
    import { NextResponse } from "next/server";
    import type { NextRequest } from "next/server";
    
    const rateLimit = new Map<string, { count: number; resetTime: number }>();
    
    export async function middleware(request: NextRequest) {
      const ip = request.ip || "unknown";
    
      // Rate limit auth endpoints
      if (request.nextUrl.pathname.startsWith("/api/auth/")) {
        const now = Date.now();
        const windowMs = 15 * 60 * 1000; // 15 minutes
        const maxRequests = 5;
    
        const record = rateLimit.get(ip);
    
        if (!record || now > record.resetTime) {
          rateLimit.set(ip, {
            count: 1,
            resetTime: now + windowMs,
          });
        } else {
          record.count++;
    
          if (record.count > maxRequests) {
            return NextResponse.json(
              { error: "Too many requests" },
              { status: 429, headers: { "Retry-After": "60" } }
            );
          }
        }
      }
    
      return NextResponse.next();
    }
    

    Account Lockout

    // lib/auth-security.ts
    export class AuthSecurityService {
      private static readonly MAX_ATTEMPTS = 5;
      private static readonly LOCKOUT_DURATION = 15 * 60 * 1000;
    
      static async checkLockout(identifier: string): Promise<boolean> {
        const attempts = await redis.get(`auth:attempts:${identifier}`);
        if (!attempts) return false;
    
        const { count, lockUntil } = JSON.parse(attempts);
    
        if (count >= this.MAX_ATTEMPTS && Date.now() < lockUntil) {
          return true;
        }
    
        if (Date.now() > lockUntil) {
          await redis.del(`auth:attempts:${identifier}`);
        }
    
        return false;
      }
    
      static async recordFailedAttempt(identifier: string): Promise<void> {
        const key = `auth:attempts:${identifier}`;
        const current = await redis.get(key);
    
        if (!current) {
          await redis.setex(
            key,
            this.LOCKOUT_DURATION / 1000,
            JSON.stringify({
              count: 1,
              lockUntil: Date.now() + this.LOCKOUT_DURATION,
            })
          );
          return;
        }
    
        const { count } = JSON.parse(current);
        const newCount = count + 1;
    
        if (newCount >= this.MAX_ATTEMPTS) {
          await this.logSecurityEvent({
            type: "ACCOUNT_LOCKED",
            identifier,
            timestamp: new Date(),
          });
        }
    
        await redis.setex(
          key,
          this.LOCKOUT_DURATION / 1000,
          JSON.stringify({
            count: newCount,
            lockUntil: Date.now() + this.LOCKOUT_DURATION,
          })
        );
      }
    
      static async resetAttempts(identifier: string): Promise<void> {
        await redis.del(`auth:attempts:${identifier}`);
      }
    
      static async logSecurityEvent(event: {
        type: string;
        identifier: string;
        timestamp: Date;
        userAgent?: string;
        ip?: string;
      }) {
        await db.securityEvents.create(event);
      }
    }
    

    Database Schema

    -- Users table
    CREATE TABLE users (
      id TEXT PRIMARY KEY,
      email TEXT UNIQUE NOT NULL,
      name TEXT NOT NULL,
      password_hash TEXT,
      role TEXT NOT NULL DEFAULT 'user',
      mfa_enabled BOOLEAN DEFAULT false,
      mfa_secret TEXT,
      mfa_enabled_at TIMESTAMP,
      active BOOLEAN DEFAULT true,
      created_at TIMESTAMP DEFAULT NOW()
    );
    
    -- WebAuthn credentials
    CREATE TABLE webauthn_credentials (
      id SERIAL PRIMARY KEY,
      user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
      credential_id BYTEA UNIQUE NOT NULL,
      credential_public_key BYTEA NOT NULL,
      counter BIGINT NOT NULL,
      transports TEXT[],
      created_at TIMESTAMP DEFAULT NOW()
    );
    
    -- Security events
    CREATE TABLE security_events (
      id SERIAL PRIMARY KEY,
      type TEXT NOT NULL,
      identifier TEXT NOT NULL,
      timestamp TIMESTAMP NOT NULL,
      user_agent TEXT,
      ip TEXT,
      metadata JSON
    );
    

    Best Practices

    Password Security

    import bcrypt from "bcryptjs";
    
    // Hash password
    const hashPassword = async (password: string) => {
      return await bcrypt.hash(password, 12);
    };
    
    // Verify password
    const verifyPassword = async (password: string, hash: string) => {
      return await bcrypt.compare(password, hash);
    };
    

    JWT Security

    import jwt from "jsonwebtoken";
    
    const generateToken = (payload: any) => {
      return jwt.sign(payload, process.env.JWT_SECRET!, {
        expiresIn: "7d",
        algorithm: "HS256",
      });
    };
    
    const verifyToken = (token: string) => {
      return jwt.verify(token, process.env.JWT_SECRET!);
    };
    

    Related Skills

    • moai-security-devsecops: Security automation
    • moai-security-encryption: Data encryption
    • moai-security-ssrf: SSRF prevention

    Validation Checklist

    Authentication:

    • NextAuth.js configured
    • OAuth providers set up
    • Credentials provider implemented
    • Session management configured

    MFA:

    • TOTP implementation complete
    • QR code generation working
    • MFA verification tested
    • Backup codes generated

    WebAuthn:

    • Passkey registration working
    • Passkey authentication working
    • Credential storage configured
    • Challenge management implemented

    Security:

    • Rate limiting active
    • Account lockout configured
    • Security events logged
    • Password hashing secure

    Last Updated: 2025-11-20

    Recommended Servers
    Vercel Grep
    Vercel Grep
    Clerk
    Clerk
    Svelte
    Svelte
    Repository
    jg-chalk-io/nora-livekit