express-openid-connect and oidc-provider are both essential libraries for working with OpenID Connect (OIDC) in Node.js, but they serve opposite ends of the authentication flow. express-openid-connect acts as a client (Relying Party) that integrates login functionality into an existing web application using an external identity provider. oidc-provider acts as a server (OpenID Provider) that issues tokens and manages user identities, effectively allowing you to build your own authentication service like Auth0 or Okta.
Both express-openid-connect and oidc-provider are mature libraries for handling OpenID Connect (OIDC) in Node.js environments, but they solve fundamentally different problems. One is designed to consume authentication, while the other is designed to provide it. Understanding this distinction is critical for architecting secure systems.
express-openid-connect acts as a Relying Party (RP).
// express-openid-connect: Configuring the client
const { auth } = require('express-openid-connect');
app.use(auth({
auth0: {
domain: 'your-tenant.auth0.com',
clientId: 'your-client-id'
},
baseURL: 'http://localhost:3000',
secret: 'long-secret-string'
}));
oidc-provider acts as an OpenID Provider (OP).
// oidc-provider: Configuring the server
const Provider = require('oidc-provider');
const provider = new Provider('http://localhost:3000', {
findAccount: async (ctx, id) => {
// Logic to find user in your database
return { accountId: id };
},
scopes: ['openid', 'profile', 'email']
});
app.use(provider.callback);
express-openid-connect favors convention over configuration.
// express-openid-connect: Minimal setup
app.use(auth({
issuerBaseURL: 'https://your-idp.com',
baseURL: 'https://your-app.com',
clientID: 'client-id',
secret: 'secret'
}));
// Routes are protected automatically with authRequired
oidc-provider requires explicit definition of almost every behavior.
// oidc-provider: Explicit interaction policy
const policy = Provider.interactionPolicy.base();
policy.add(new Provider.interactionPolicy.Prompt({
name: 'login',
requestable: true
}));
const provider = new Provider('http://localhost:3000', {
interactions: { policy }
});
express-openid-connect provides middleware to guard your routes.
// express-openid-connect: Protecting a route
app.get('/dashboard', authRequired, (req, res) => {
res.send(`Hello ${req.oidc.user.name}`);
});
app.get('/login', (req, res) => {
res.oidc.login({ returnTo: '/dashboard' });
});
oidc-provider mounts to your app to handle incoming auth requests.
// oidc-provider: Mounting the provider
// In your Express app
app.use('/oidc', provider.callback);
// You still need to build your own login form UI
app.get('/login', (req, res) => {
res.send('<form>...</form>'); // Custom UI implementation
});
express-openid-connect manages the user session for your app.
req.oidc.user without extra code.// express-openid-connect: Accessing session
app.get('/profile', authRequired, (req, res) => {
// Session is automatically populated
const user = req.oidc.user;
res.json(user);
});
oidc-provider expects you to manage sessions for the Identity Server.
findAccount and session persistence logic.// oidc-provider: Implementing account lookup
const config = {
findAccount: async (ctx, id, token) => {
// You must fetch from your DB
const user = await db.users.findById(id);
return {
accountId: user.id,
claims: () => ({ sub: user.id, name: user.name })
};
}
};
While their roles differ, both libraries share underlying standards and dependencies.
openid, profile, and email.// Both support standard scopes
// express-openid-connect config
{ scopes: ['openid', 'profile'] }
// oidc-provider config
{ scopes: ['openid', 'profile'] }
// Both integrate with Express
const express = require('express');
const app = express();
// Works with either package
app.use(express.json());
// express-openid-connect: Secure cookies
{ session: { cookie: { secure: true } } }
// oidc-provider: Secure cookies
{ cookies: { secure: true } }
// express-openid-connect: After login hook
{ afterCallback: (req, res, session, state) => {
session.myCustomClaim = 'value';
}}
// oidc-provider: Extra claims
{ claims: { extra: ['myCustomClaim'] } }
// Both follow semantic versioning
// Check package.json for latest versions
| Feature | Shared by Both |
|---|---|
| Protocol | 🔐 OpenID Connect & OAuth 2.0 |
| Runtime | 🚀 Node.js & Express |
| Security | 🛡️ HTTPS & Encryption support |
| Extensibility | 🔧 Custom claims & hooks |
| Maintenance | 📦 Active community support |
| Feature | express-openid-connect | oidc-provider |
|---|---|---|
| Role | 🧑💻 Client (Relying Party) | 🏢 Server (Identity Provider) |
| Setup Time | ⚡ Minutes (Hosted IdP) | 🐢 Days/Weeks (Self-hosted) |
| User Storage | 🚫 External (Auth0, etc.) | 💾 Your Database |
| UI Control | 🎨 Hosted Login Pages | 🖌️ Fully Custom Login UI |
| Responsibility | ✅ Low (Vendor manages security) | ⚠️ High (You manage security) |
express-openid-connect is like renting a secure apartment 🏢—everything is maintained for you, and you just move in. Ideal for startups, SaaS products, and teams that want to ship features without becoming security experts.
oidc-provider is like building your own house from the ground up 🏗️—you control every brick, but you are responsible for the roof not leaking. Best for enterprises with strict compliance needs, legacy user databases, or those building an identity platform as a product.
Final Thought: Do not build your own identity server unless you have to. Start with express-openid-connect and a managed provider. Only reach for oidc-provider when business requirements demand full ownership of the authentication flow.
Choose oidc-provider if you need to build your own identity server to issue tokens for multiple applications or devices. It is the right choice when you require full control over user storage, login UI, token issuance rules, and compliance with specific OIDC certification requirements. Use this only if you have the security expertise to maintain an authentication server, as it shifts the burden of safety onto your team.
Choose express-openid-connect if you are building a standard web application and need to add user login quickly using an existing identity provider like Auth0, Okta, or Azure AD. It handles the complex OIDC callback exchanges and session management for you, letting you focus on your app's features rather than security protocols. This is the ideal starting point for most teams who do not need to manage user credentials directly.
This module provides an OAuth 2.0 (RFC 6749) Authorization Server with support for OpenID Connect (OIDC) and many other additional features and standards.
Table of Contents
The following specifications are implemented by oidc-provider (not exhaustive):
Note that not all features are enabled by default, check the configuration section on how to enable them.
RFC6749 - OAuth 2.0 & OIDC Core 1.0Discovery 1.0 & RFC8414 Authorization Server MetadataRP-Initiated Logout 1.0Back-Channel Logout 1.0RFC7009 - OAuth 2.0 Token RevocationRFC7636 - Proof Key for Code Exchange (PKCE)RFC7662 - OAuth 2.0 Token IntrospectionRFC8252 - OAuth 2.0 for Native Apps BCP (AppAuth)RFC8628 - OAuth 2.0 Device Authorization Grant (Device Flow)RFC8705 - OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens (MTLS)RFC8707 - OAuth 2.0 Resource IndicatorsRFC9101 - OAuth 2.0 JWT-Secured Authorization Request (JAR)RFC9126 - OAuth 2.0 Pushed Authorization Requests (PAR)RFC9207 - OAuth 2.0 Authorization Server Issuer Identifier in Authorization ResponseRFC9449 - OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP)RFC9701 - JWT Response for OAuth Token IntrospectionFAPI 1.0)FAPI 2.0)FAPI 2.0)JARM)CIBA)Supported Access Token formats:
The following specifications and drafts are implemented as experimental features:
FAPI-CIBA) - Implementers Draft 01CIMD) - Draft 01Updates to experimental feature specification versions are released as MINOR library versions,
if you utilize these features consider using the tilde ~ operator in your
package.json since breaking changes may be introduced as part of these version updates. Alternatively
acknowledge the version and be notified of breaking changes as part of
your CI.

Filip Skokan has certified that oidc-provider
conforms to the following profiles of the OpenID Connect™ protocol.
If you want to quickly add OpenID Connect authentication to Node.js apps, feel free to check out Auth0's Node.js SDK and free plan. Create an Auth0 account; it's free!
If you or your company use this module, or you need help using/upgrading the module, please consider becoming a sponsor so I can continue maintaining it and adding new features carefree. The only way to guarantee you get feedback from the author & sole maintainer of this module is to support the package through GitHub Sponsors.
oidc-provider can be mounted to existing connect, express, fastify, hapi, or koa applications, see how. The authorization server allows to be extended and configured in various ways to fit a variety of uses. See the documentation and example folder.
import * as oidc from "oidc-provider";
const provider = new oidc.Provider("http://localhost:3000", {
// refer to the documentation for other available configuration
clients: [
{
client_id: "foo",
client_secret: "bar",
redirect_uris: ["http://localhost:8080/cb"],
// ... other client properties
},
],
});
const server = provider.listen(3000, () => {
console.log(
"oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration",
);
});
External type definitions are available via DefinitelyTyped.
Collection of Community-maintained configuration use cases are in the Community Guides Discussions section
oidc-provider instances are event emitters, using event handlers you can hook into the various actions and i.e. emit metrics that react to specific triggers. See the list of available emitted event names and their description.
| Version | Security Fixes 🔑 | Other Bug Fixes 🐞 | New Features ⭐ |
|---|---|---|---|
| v9.x | Security Policy | ✅ | ✅ |
| v8.x | Security Policy | ❌ | ❌ |