Fastify OpenID Connect Authentication
fastify-openid-auth is a specialized Fastify plugin designed to streamline OpenID Connect (OIDC) authentication within a Fastify application. Leveraging the robust openid-client library, it offers comprehensive support for both bearer token and cookie-based authentication flows, providing flexibility in how authentication state and tokens are managed. The plugin currently operates at version 12.0.0 as of its latest significant update in late 2025, demonstrating a consistent and active development cadence with several minor and major releases throughout 2024 and 2025. Key differentiators include its deep integration into the Fastify ecosystem by decorating the Fastify instance with dedicated `login`, `verify`, `refresh`, and `logout` handlers, full TypeScript type definitions, and adaptable token reading/writing strategies via headers, cookies, or session storage, making it suitable for a wide range of web application and API authentication scenarios.
Common errors
-
FST_ERR_DEC_ALREADY_PRESENT: The decorator 'AUTH_HANDLERS_SYMBOL' is already present!
cause Attempting to register `fastify-openid-auth` multiple times with the same decorator symbol, or another plugin using the same symbol.fixEnsure `fastify.register(openIDAuthPlugin, ...)` is called only once per Fastify instance, or use a unique `decorator` symbol/string if you intend to register it multiple times for different configurations (though this is rare). -
TypeError: Cannot read properties of undefined (reading 'get') at OpenIDLoginHandler (file:///path/to/fastify-openid-auth/dist/index.js:XXX:YY)
cause The `session` object within the handler options (`login`, `verify`, etc.) is either missing or its `get`, `set`, `destroy` functions are not properly defined. This is especially relevant after v12.0.0 and typically indicates a problem with session plugin integration.fixEnsure a Fastify session plugin (e.g., `@fastify/session`) is registered *before* `fastify-openid-auth`. Then, explicitly provide the `session` object with `get`, `set`, and `destroy` methods in your `login`, `verify`, `refresh`, and `logout` options, mapping them to your session plugin's interface (e.g., `request.session`). -
Plugin 'fastify-openid-auth' was registered against Fastify version '4.x.x' but is being used with Fastify version '5.x.x.'
cause Version mismatch between `fastify-openid-auth` and the installed `fastify` package, specifically related to the breaking change in v10.0.0 that requires Fastify 5.x.fixUpgrade your project's `fastify` dependency to `5.x` (or newer) if using `fastify-openid-auth@10.0.0+`. Alternatively, if you must use Fastify 4.x, downgrade `fastify-openid-auth` to a compatible version (e.g., `<10.0.0`).
Warnings
- breaking In `v12.0.0`, the internal `session` configuration for handlers like `OpenIDLoginHandler` was externalized. This means `session` configuration options must now be explicitly passed within `OpenIDLoginHandlerOptions` (and similar handler options) and are no longer automatically inferred or handled internally by the plugin. This makes `options` explicitly required for `OpenIDLoginHandler`.
- breaking `v11.0.0` upgraded the underlying `openid-client` to `v6` and `jose` to `v6`. These are significant upgrades to core dependencies and introduce breaking changes that may affect how you configure your OpenID client, handle tokens, or interact with OIDC provider metadata.
- breaking `fastify-openid-auth@10.0.0` introduced a breaking change requiring `fastify` version `5.x` or higher. Using older versions of Fastify with this plugin will lead to compatibility issues and errors.
- gotcha For cookie-based authentication, `fastify-openid-auth` relies on a Fastify session plugin (e.g., `@fastify/session`) to manage user sessions and store OIDC state/tokens. Incorrectly configured or missing session management can lead to security vulnerabilities, lost state, and failed authentication flows.
Install
-
npm install fastify-openid-auth -
yarn add fastify-openid-auth -
pnpm add fastify-openid-auth
Imports
- openIDAuthPlugin
const openIDAuthPlugin = require('fastify-openid-auth')import openIDAuthPlugin from 'fastify-openid-auth'
- openIDHandlersFactory
import openIDHandlersFactory from 'fastify-openid-auth'
import { openIDHandlersFactory } from 'fastify-openid-auth' - OpenIDLoginHandlerOptions
import { OpenIDLoginHandlerOptions } from 'fastify-openid-auth'import type { OpenIDLoginHandlerOptions } from 'fastify-openid-auth'
Quickstart
import Fastify from 'fastify';
import openIDAuthPlugin from 'fastify-openid-auth';
import { generators } from 'openid-client'; // Often used with openid-client
// For session management, typically you'd also install and register @fastify/cookie and @fastify/session
// import fastifyCookie from '@fastify/cookie';
// import fastifySession from '@fastify/session';
const fastify = Fastify({ logger: true });
// Dummy OpenID Provider configuration (replace with actual values)
const openidConfig = {
issuer: 'https://accounts.google.com', // Example, replace with your OIDC provider
client_id: process.env.OIDC_CLIENT_ID ?? 'your-client-id-from-provider',
client_secret: process.env.OIDC_CLIENT_SECRET ?? 'your-client-secret-from-provider',
redirect_uris: ['http://localhost:3000/auth/callback'],
response_types: ['code']
};
const AUTH_HANDLERS_SYMBOL = Symbol.for('auth-handlers');
// Register cookie and session plugins FIRST if using cookie-based authentication
// fastify.register(fastifyCookie);
// fastify.register(fastifySession, {
// secret: process.env.SESSION_SECRET ?? 'a-very-long-and-secure-secret-key-for-session-encryption',
// cookie: { secure: process.env.NODE_ENV === 'production' }
// });
fastify.register(openIDAuthPlugin, {
decorator: AUTH_HANDLERS_SYMBOL,
config: openidConfig,
login: {
// Required since v12.0.0 for all handlers that manage OIDC state/tokens via session
session: {
get: async (request) => (request as any).session, // Placeholder: assumes @fastify/session is set up
set: async (request, value) => { (request as any).session = value; }, // Placeholder
destroy: async (request) => { delete (request as any).session; } // Placeholder
},
client_redirect_uri: 'http://localhost:3000/auth/callback',
parameters: {
scope: 'openid email profile',
prompt: 'login'
},
code_verifier_generator: generators.codeVerifier, // Recommended for PKCE
state_generator: generators.random // Recommended for state parameter
},
verify: {
session: {
get: async (request) => (request as any).session,
set: async (request, value) => { (request as any).session = value; },
destroy: async (request) => { delete (request as any).session; }
}
}
});
fastify.get('/login', async (request, reply) => {
const { login } = (fastify as any)[AUTH_HANDLERS_SYMBOL];
await login(request, reply);
});
fastify.get('/auth/callback', async (request, reply) => {
const { verify } = (fastify as any)[AUTH_HANDLERS_SYMBOL];
try {
const tokens = await verify(request, reply);
reply.send({ message: 'Login successful!', tokens });
} catch (error: any) {
fastify.log.error(error);
reply.status(401).send({ error: 'Authentication failed', details: error.message });
}
});
fastify.get('/protected', async (request, reply) => {
// In a real application, you would add authentication middleware here
// to check for a valid session or bearer token after 'verify' has run.
reply.send({ message: 'This is a protected route. Requires prior authentication.' });
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
fastify.log.info(`Server listening on port ${3000}`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();