Koa Passport Authentication Middleware
koa-passport is a middleware that integrates the popular Passport.js authentication library with the Koa web framework. It simplifies the process of adding various authentication strategies (like local username/password, OAuth, JWT) to Koa applications by exposing Passport's core functionalities directly on the Koa `ctx` object. The current stable version is 6.x, which is designed to work with Koa 2.x and Passport.js 5.x or 6.x. Its release cadence is closely tied to the major versions of its upstream `passport` and `koa` dependencies, ensuring compatibility. Key differentiators include its idiomatic Koa middleware interface, providing `ctx.login()`, `ctx.logout()`, `ctx.isAuthenticated()`, and `ctx.isUnauthenticated()` methods, which streamline user session management and status checks within Koa's async context. It requires external session management middleware like `koa-session` and typically a body parser for credential submission.
Common errors
-
TypeError: ctx.isAuthenticated is not a function
cause The `passport.initialize()` or `passport.session()` middleware has not been applied to the Koa application, or they are not in the correct order.fixEnsure `app.use(passport.initialize())` and `app.use(passport.session())` are called on your Koa app after session middleware and before any routes that use Passport's context methods. -
Error: Session is not configured!
cause Passport's session management (`passport.session()`) requires a Koa session middleware (e.g., `koa-session`) to be set up and active.fixInstall and configure `koa-session` (or a similar session middleware) by calling `app.use(session({}, app))` before `app.use(passport.session())`. Remember to set `app.keys` for session signing. -
Error: Unknown authentication strategy "local"
cause The authentication strategy (e.g., `passport-local`, `passport-google-oauth2`) has not been properly defined and registered with Passport.fixEnsure you have imported the necessary Passport strategy (e.g., `require('passport-local').Strategy`) and called `passport.use(new LocalStrategy(...))` before `passport.initialize()`.
Warnings
- breaking `koa-passport` major versions are tightly coupled with `passport` and `koa` major versions. Upgrading one without the others can lead to incompatibility issues.
- breaking The `logout` function's signature changed in `koa-passport` 6.0.0 from a callback-based `(options, callback) => void` to a Promise-based `(options) => Promise<void>`.
- breaking In `koa-passport` v3.0.0, `ctx.passport` was removed, and state variables like `_passport` and `user` were moved to `ctx.state`. Passport no longer monkey patches `http.IncomingMessage` directly.
- gotcha `koa-passport` relies on external session middleware (like `koa-session`) and typically a body parser (like `koa-bodyparser`) to function correctly, especially for session-based authentication and handling login form submissions.
Install
-
npm install koa-passport -
yarn add koa-passport -
pnpm add koa-passport
Imports
- passport
import passport from 'koa-passport';
const passport = require('koa-passport'); - Passport
import Passport from 'passport'; import { Strategy as LocalStrategy } from 'passport-local';const Passport = require('passport'); const LocalStrategy = require('passport-local').Strategy; - Koa
import Koa from 'koa';
const Koa = require('koa');
Quickstart
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const session = require('koa-session');
const passport = require('koa-passport');
const LocalStrategy = require('passport-local').Strategy;
const app = new Koa();
// Session Configuration
app.keys = ['your-secret-key']; // Replace with a strong, random key in production
app.use(session({}, app));
// Body Parser
app.use(bodyParser());
// Passport Initialization
app.use(passport.initialize());
app.use(passport.session());
// Define a local authentication strategy
passport.use(new LocalStrategy(
function(username, password, done) {
// Simulate user lookup
if (username === 'test' && password === 'password') {
return done(null, { id: 1, username: 'test' });
} else {
return done(null, false);
}
}
));
// Serialize and Deserialize user for session management
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
// Simulate user lookup by ID
done(null, { id: id, username: 'test' });
});
// Routes
app.use(async ctx => {
if (ctx.path === '/login' && ctx.method === 'POST') {
return passport.authenticate('local', async (err, user, info, status) => {
if (user) {
await ctx.login(user);
ctx.body = `Hello, ${user.username}! You are logged in.`;
} else {
ctx.status = 401;
ctx.body = 'Login failed.';
}
})(ctx);
} else if (ctx.path === '/logout') {
ctx.logout();
ctx.redirect('/');
} else if (ctx.path === '/profile') {
if (ctx.isAuthenticated()) {
ctx.body = `Welcome back, ${ctx.state.user.username}!`;
} else {
ctx.redirect('/login-page'); // Redirect to a login page
}
} else if (ctx.path === '/login-page') {
ctx.body = `
<h1>Login</h1>
<form action="/login" method="post">
<input type="text" name="username" placeholder="username" />
<input type="password" name="password" placeholder="password" />
<button type="submit">Login</button>
</form>
`;
} else {
ctx.body = `
<h1>Home</h1>
<p>Status: ${ctx.isAuthenticated() ? 'Logged In' : 'Logged Out'}</p>
<p><a href="/login-page">Login</a></p>
<p><a href="/profile">Profile</a></p>
${ctx.isAuthenticated() ? '<p><a href="/logout">Logout</a></p>' : ''}
`;
}
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));