Implements Passport.js authentication middleware with local, OAuth, and JWT strategies for Express/Node.js. Use when building Node.js APIs, implementing custom auth flows, or needing flexible authentication strategies.
Adds Passport.js authentication middleware to Express/Node.js apps. Use when building APIs that need local username/password, OAuth (Google/GitHub), or JWT token authentication with session management.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/oauth.mdreferences/sessions.mdPassport.js is authentication middleware for Node.js. It supports 500+ authentication strategies including local username/password, OAuth, JWT, and more.
npm install passport express-session
npm install passport-local # For username/password
// app.ts
import express from 'express'
import session from 'express-session'
import passport from 'passport'
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}))
app.use(passport.initialize())
app.use(passport.session())
// config/passport.ts
import passport from 'passport'
import { Strategy as LocalStrategy } from 'passport-local'
import bcrypt from 'bcrypt'
import { getUserByEmail, getUserById } from './db'
passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password'
},
async (email, password, done) => {
try {
const user = await getUserByEmail(email)
if (!user) {
return done(null, false, { message: 'Invalid email or password' })
}
const isValid = await bcrypt.compare(password, user.passwordHash)
if (!isValid) {
return done(null, false, { message: 'Invalid email or password' })
}
return done(null, user)
} catch (error) {
return done(error)
}
}
))
// Serialize user to session
passport.serializeUser((user: any, done) => {
done(null, user.id)
})
// Deserialize user from session
passport.deserializeUser(async (id: string, done) => {
try {
const user = await getUserById(id)
done(null, user)
} catch (error) {
done(error)
}
})
// routes/auth.ts
import express from 'express'
import passport from 'passport'
const router = express.Router()
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err)
}
if (!user) {
return res.status(401).json({ error: info?.message || 'Login failed' })
}
req.logIn(user, (err) => {
if (err) {
return next(err)
}
return res.json({ user: { id: user.id, email: user.email } })
})
})(req, res, next)
})
router.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) {
return next(err)
}
res.json({ message: 'Logged out' })
})
})
router.get('/me', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' })
}
res.json({ user: req.user })
})
export default router
// middleware/auth.ts
import { Request, Response, NextFunction } from 'express'
export function isAuthenticated(req: Request, res: Response, next: NextFunction) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).json({ error: 'Authentication required' })
}
export function hasRole(role: string) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: 'Authentication required' })
}
if ((req.user as any).role !== role) {
return res.status(403).json({ error: 'Insufficient permissions' })
}
next()
}
}
import { isAuthenticated, hasRole } from './middleware/auth'
// Protected route
app.get('/dashboard', isAuthenticated, (req, res) => {
res.json({ data: 'Protected data' })
})
// Admin only
app.get('/admin', hasRole('admin'), (req, res) => {
res.json({ data: 'Admin data' })
})
For stateless API authentication.
npm install passport-jwt jsonwebtoken
npm install -D @types/jsonwebtoken @types/passport-jwt
// config/passport-jwt.ts
import passport from 'passport'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
import { getUserById } from './db'
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET!,
algorithms: ['HS256'] as const
}
passport.use(new JwtStrategy(options, async (payload, done) => {
try {
const user = await getUserById(payload.sub)
if (!user) {
return done(null, false)
}
return done(null, user)
} catch (error) {
return done(error, false)
}
}))
import jwt from 'jsonwebtoken'
function generateToken(user: { id: string; email: string }) {
return jwt.sign(
{ sub: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
)
}
router.post('/login', async (req, res, next) => {
passport.authenticate('local', { session: false }, (err, user, info) => {
if (err) return next(err)
if (!user) return res.status(401).json({ error: info?.message })
const token = generateToken(user)
res.json({ token, user: { id: user.id, email: user.email } })
})(req, res, next)
})
router.get('/protected',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.json({ user: req.user })
}
)
npm install passport-google-oauth20
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: '/auth/google/callback',
scope: ['email', 'profile']
},
async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user
let user = await getUserByGoogleId(profile.id)
if (!user) {
user = await createUser({
googleId: profile.id,
email: profile.emails?.[0].value,
name: profile.displayName,
avatar: profile.photos?.[0].value
})
}
return done(null, user)
} catch (error) {
return done(error as Error)
}
}
))
// Start OAuth flow
router.get('/auth/google',
passport.authenticate('google', { scope: ['email', 'profile'] })
)
// Handle callback
router.get('/auth/google/callback',
passport.authenticate('google', {
failureRedirect: '/login',
successRedirect: '/dashboard'
})
)
npm install passport-github2
import { Strategy as GitHubStrategy } from 'passport-github2'
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: '/auth/github/callback'
},
async (accessToken, refreshToken, profile, done) => {
try {
let user = await getUserByGithubId(profile.id)
if (!user) {
user = await createUser({
githubId: profile.id,
email: profile.emails?.[0].value,
name: profile.displayName || profile.username,
avatar: profile.photos?.[0].value
})
}
done(null, user)
} catch (error) {
done(error as Error)
}
}
))
router.get('/auth/github',
passport.authenticate('github', { scope: ['user:email'] })
)
router.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard')
}
)
// User model supports multiple auth methods
interface User {
id: string
email: string
passwordHash?: string
googleId?: string
githubId?: string
}
// Find user by any method
async function findOrCreateUser(profile: OAuthProfile) {
// Check for existing user by email first
let user = await getUserByEmail(profile.email)
if (user) {
// Link OAuth account to existing user
await linkOAuthAccount(user.id, profile.provider, profile.id)
return user
}
// Create new user
return createUser({
email: profile.email,
[profile.provider + 'Id']: profile.id,
name: profile.name
})
}
npm install connect-redis redis
import RedisStore from 'connect-redis'
import { createClient } from 'redis'
const redisClient = createClient({ url: process.env.REDIS_URL })
await redisClient.connect()
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000
}
}))
npm install connect-pg-simple
import pgSession from 'connect-pg-simple'
import { pool } from './db'
const PostgresStore = pgSession(session)
app.use(session({
store: new PostgresStore({
pool,
tableName: 'sessions'
}),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false
}))
// types/express.d.ts
declare global {
namespace Express {
interface User {
id: string
email: string
name?: string
role?: string
}
}
}
export {}
// Custom authentication error handler
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
console.error('Auth error:', err)
return res.status(500).json({ error: 'Authentication failed' })
}
if (!user) {
return res.status(401).json({
error: info?.message || 'Invalid credentials',
code: 'INVALID_CREDENTIALS'
})
}
req.logIn(user, (err) => {
if (err) {
console.error('Session error:', err)
return res.status(500).json({ error: 'Session creation failed' })
}
res.json({
user: {
id: user.id,
email: user.email,
name: user.name
}
})
})
})(req, res, next)
})
import request from 'supertest'
import app from './app'
describe('Authentication', () => {
it('should login with valid credentials', async () => {
const res = await request(app)
.post('/auth/login')
.send({ email: 'test@example.com', password: 'password' })
.expect(200)
expect(res.body.user.email).toBe('test@example.com')
})
it('should reject invalid credentials', async () => {
await request(app)
.post('/auth/login')
.send({ email: 'test@example.com', password: 'wrong' })
.expect(401)
})
it('should protect routes', async () => {
await request(app)
.get('/dashboard')
.expect(401)
})
})
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.