express-openid-connect: protecting an Express app with OpenID Connect
An Express.js middleware to protect OpenID Connect web applications.
At a glance
- What is it?
- express-openid-connect is an Express middleware that puts an OpenID Connect provider in front of your routes, storing the session in an encrypted and signed cookie, and it is the generic library rather than the vendor-specific one. It suits a team that wants provider-neutral sign-on in a Node service, and Auth0 users are pointed at a different package for new projects.
- Who is it for?
- Adopt express-openid-connect when you run an Express service and want provider-neutral OpenID Connect sign-on with session handling and token access already built, since the middleware covers the login routes, the session cookie and the token helpers for you.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A generic library that Auth0 points new projects away from
express-openid-connect is an Express.js middleware to protect OpenID Connect web applications, and the README says it plainly. It is a generic OpenID Connect library that works with any provider.
That word generic is the whole design position, and the README is unusually direct about what it means for the project's future. It will keep getting generic features, but new Auth0-specific features will only be added to `@auth0/auth0-express`. If you already use this library with Auth0, you do not have to move right now, and when you are ready there is a migration guide that supports a zero-downtime migration so your users stay logged in.
So the two packages have different jobs. `@auth0/auth0-express` is the official Auth0 SDK for Express and is stable since v1.0.0. `express-openid-connect` is the standards-shaped library, and the split is between a vendor product and a protocol implementation.
For a team whose identity provider might change, or that is not on Auth0 at all, that split is the reason to choose this package. For a team that is on Auth0 and starting fresh, following the README's advice costs nothing and saves a future migration.
The licence is MIT, the primary language is JavaScript, and the repository is not archived, with the last push on 2026-10-19. The recent releases are v3.4.0 on 2026-08-07, v3.3.0 on 2026-07-31 and v3.2.1 on 2026-07-24, so the v3 line is moving and the minor version numbers are where the API changes land.
Four configuration values and where they come from
The library needs four values to request and accept authentication: `issuerBaseURL`, `baseURL`, `clientID` and `secret`. The README says they can be configured with environment variables:
ISSUER_BASE_URL=https://YOUR_DOMAIN
CLIENT_ID=YOUR_CLIENT_ID
BASE_URL=https://YOUR_APPLICATION_ROOT_URL
SECRET=LONG_RANDOM_VALUEor in the library initialisation:
// index.js
const { auth } = require('express-openid-connect');
app.use(
auth({
issuerBaseURL: 'https://YOUR_DOMAIN',
baseURL: 'https://YOUR_APPLICATION_ROOT_URL',
clientID: 'YOUR_CLIENT_ID',
secret: 'LONG_RANDOM_STRING',
idpLogout: true,
}),
);Two details in that snippet are worth understanding. The `secret` is the key the session cookie is encrypted and signed with, and it has to be a long random value, because a predictable secret means a forgeable session. The `baseURL` has to match the origin your application actually serves from, since the callback and logout URLs are derived from it.
The `idpLogout: true` option is the difference between logging out of your application and logging out of the identity provider as well. The README's section on deploying multiple apps on the same domain explains why the last option matters in a different way: if you host more than one application under the same domain, for example `example.com/app1` and `example.com/app2`, each app must be given a unique session cookie name, transaction cookie name and cookie path, otherwise the apps share the same cookie namespace and can silently overwrite each other's session.
That failure mode is worth noting, because it is silent. Nothing errors, one user's session simply replaces another's.
What the middleware does once it is mounted
With the basic configuration above, the README says your application will require authentication for all routes and store the user identity in an encrypted and signed cookie. That is the whole default behaviour, and it is the design decision that shapes everything else.
The session lives in the cookie rather than in server-side session storage. The `index.js` in the tree and the `lib/` and `middleware/` directories hold the implementation, and the dependency list explains the mechanics. `openid-client` at 6.1.3 is the protocol implementation, `jose` at 6.1.3 handles the JSON Web Token signing and encryption, `cookie` manages the cookie parsing and serialisation, `base64url` encodes the session payload, `on-headers` lets the middleware rewrite response headers, and `joi` validates the configuration object. `http-errors`, `debug`, `clone`, `url-join` and `util-promisify` fill out the supporting work.
Because there is no server-side session store in the default setup, the deployment story is simpler and the scaling story is different. A stateless cookie session means any instance can serve any request, which is convenient, and it means the session content travels to the browser on every request and cannot be revoked centrally without a blocklist. The `examples/` directory shows the alternatives: `custom-session-store.js` for storing sessions yourself, and `custom-routes.js` for route-specific authentication rather than protecting everything.
Other examples cover the protocol features you would expect to need: `backchannel-logout.js` and its variants for back-channel logout, `client-secret-jwt.js` and `private-key-jwt.js` for client authentication methods, `mtls.js` for mutual TLS, `cte-session-transfer.js` and `custom-token-exchange.js` for token exchange, `iframe.js` for silent authentication in a frame, `jar.js` for session transfer, `userinfo.js` and `access-an-api.js` for calling APIs, and `validate_claims.js` for claim checking. The breadth of that list is a good indication of what the library actually covers.
Error handling, and the escaping trap in the default handler
The error handling section is short and the advice in it is specific enough to be worth quoting carefully.
Errors raised by this library are handled by the default Express error handler, which, in the interests of security, does not include the stack trace or error message in the production environment. If you write your own error handler, you should not render the error message or the OAuth `error` and `error_description` properties without using a templating engine that will properly escape them first.
That is a real trap. The OAuth error properties are strings that came from an identity provider, and an identity provider is an external system that can put arbitrary content in them. Rendering one of them into an HTML error page without escaping is a reflected injection route, and the fact that the default handler is cautious about exactly this tells you the library's authors thought about it.
The second part of the guidance is about where the documentation lives. The Express documentation on writing custom error handlers is the reference, and `EXAMPLES.md` has the comprehensive examples for route-specific authentication, custom application session handling, and requesting and using access tokens for external APIs. There is also a separate `FAQ.md` and a `TROUBLESHOOTING.md` in the repository.
The `index.d.ts` file at the top level and the `index.test-d.ts` next to it show that the package ships its own TypeScript definitions, with the `types` test script running `tsd .`.
One security point about the whole library is in the Getting Started section. You are asked to follow the Secure Local Development guide to ensure that applications using this library are running over secure channels, HTTPS URLs, and the README warns that applications using this library without HTTPS may experience invalid state errors. That is the state parameter check, and the reason it fires is that the library refuses to hand back a session cookie over an insecure channel.
Setting up the provider side, and the settings people miss
Before the library can be configured, the application has to exist on the provider side, and the README is specific about the settings.
You create a Regular Web Application in the Auth0 Dashboard, and for an existing application the README lists what to verify. The Authentication Methods setting in the Credentials tab is set to None. Under Advanced Settings, on the OAuth tab, the JsonWebToken Signature Algorithm is set to `RS256` and OIDC Conformant is enabled. Then the Application URIs section needs an Allowed Callback URL and an Allowed Logout URL, which in the example are both `http://localhost:3000`.
That Authentication Methods setting being None is the detail most likely to cause a confusing failure. It is the switch that says the client does not authenticate with a client secret on the token endpoint, which is what the public client model of a browser-side app requires. The RS256 setting matters for the other end of the trust relationship, since the library verifies the ID token signature with `jose`, and an algorithm the library does not expect will be rejected.
The URLs should reflect the origins your application actually runs on, and the Allowed Callback URL may include a path if that is where you handle the callback.
The two values to take away from the Dashboard are the Client ID and the Domain under Basic Information, since the Domain becomes the `issuerBaseURL` and the Client ID becomes the `clientID` in the configuration. The `SECRET` is yours to generate, and it is not the same thing as the client secret in the Auth0 Dashboard.
Where express-openid-connect is the wrong choice
The first wrong-tool case is a new Auth0 project, and this is the one the README itself calls out. The Auth0-specific features are going to `@auth0/auth0-express`, so choosing the generic library for a new Auth0 application means choosing the one that will not receive them.
The second is a service that is not a traditional web app. The middleware is built around the browser redirect flow, with a login route, a callback route, a session cookie and a logout route. A machine-to-machine service authenticating with a client credentials grant wants a different library, and the `examples/` directory, which is full of browser-flow scenarios, shows the shape of what is supported.
The third is a local development setup without HTTPS. The README says applications using this library without HTTPS may experience invalid state errors, so the Secure Local Development guide is not optional reading for a developer running the app on `http://localhost:3000`, even though the example callback URLs are plain HTTP.
The fourth is a deployment that cannot set the four required values from the environment. The whole configuration is those four values, which is a low bar, but it is a bar, and a platform that gives you no way to set environment variables is not going to work with this library or with most others.
The alternative worth naming is `@auth0/auth0-express` for an Auth0 deployment, or the lower-level `openid-client` package directly for a service that needs full control over the flow. The difference in approach is that express-openid-connect gives you the whole web application pattern in a single `app.use` call, with a session, routes and helpers already wired, while `openid-client` gives you the protocol and leaves the session and the routes to you. For a typical Express web app the middleware is the shorter path, and for a service with unusual session requirements the lower-level package is the more honest choice.
Maintenance, the v3 break, and the repository layout
The library is being worked on, with the last push on 2026-10-19 and three releases in July and August 2026.
The v2 to v3 transition is the significant upgrade event, and the README flags it in the Requirements section. The `httpAgent` config option was removed in v3, and to route OIDC requests through a proxy you now use the `customFetch` option with undici's `ProxyAgent` instead, with the full list of breaking changes in the V3 Migration Guide. There is also a separate `V2_MIGRATION_GUIDE.md` in the tree, so both transitions are documented.
The Node floor is `^20.19.0 || ^22.12.0 || >= 23.0.0`, which is the requirement the README states, and it excludes Node 18 and 20 versions below 20.19.0.
The repository layout shows the maturity of the project. There is `ARCHITECTURE.md`, `CONTRIBUTING.md`, `AGENTS.md` and `CLAUDE.md` for contributors and coding assistants, `docs/` for the typedoc output with a `typedoc.js` configuration, `end-to-end/` for browser-level tests, `test/` for the unit tests, `references/` for external documents, and an `examples/` directory with some twenty runnable scenarios. There is a `codecov.yml` and a `opslevel.yml`, and the `Makefile` is absent, with the npm scripts doing the work.
The test setup is the part worth noting for someone deciding whether to depend on it. The main test script runs mocha with an increased maximum HTTP header size, the CI script wraps it in nyc for coverage, there are separate scripts for type tests with tsd, for documentation with typedoc, and for end-to-end tests with mocha and a thirty second timeout. The dev dependencies include `oidc-provider`, which is a local OpenID provider implementation used in tests, along with `nock` for HTTP interception, `puppeteer` for the browser tests, and `connect-redis` with `redis-mock` and `memorystore` for the session store examples. A test suite that runs a real provider locally is the reason the redirect and state handling works across versions.
The licence is MIT, and the package publishes only `lib`, `middleware` and `index.d.ts` in the `files` array, so the published artefact is the implementation and the type definitions with nothing else.
Editorial conclusion
Adopt express-openid-connect when you run an Express service and want provider-neutral OpenID Connect sign-on with session handling and token access already built, since the middleware covers the login routes, the session cookie and the token helpers for you. Do not adopt it for a new Auth0 project, because the README directs those to `@auth0/auth0-express`, and do not adopt it without HTTPS in development, since the Secure Local Development guidance notes that applications running without it may hit invalid state errors. Before you deploy, check the Node version floor of `^20.19.0 || ^22.12.0 || >= 23.0.0`, read the v3 migration guide if you are coming from v2, and review the error handling note about escaping OAuth error properties in your own handler.
Frequently asked questions
What does express-openid-connect do for an Express application?
It is an Express middleware that protects web applications using OpenID Connect. With the basic configuration it requires authentication for all routes and stores the user identity in an encrypted and signed cookie, and it also provides the login and callback routes plus helpers for working with tokens.
Should I use express-openid-connect or @auth0/auth0-express?
The README says that for new projects using Auth0 you should use `@auth0/auth0-express`, the official Auth0 SDK for Express, which is stable since v1.0.0. `express-openid-connect` is the generic library for any provider, it will keep receiving generic features, and existing users do not have to move right now, with a zero-downtime migration available.
Which configuration values does express-openid-connect require?
Four: `issuerBaseURL`, `baseURL`, `clientID` and `secret`. They can be set as the environment variables `ISSUER_BASE_URL`, `BASE_URL`, `CLIENT_ID` and `SECRET`, or passed in the `auth()` options when the middleware is initialised.
What changed in version 3 of express-openid-connect?
The `httpAgent` config option was removed in v3. To route OIDC requests through a proxy you use the `customFetch` option with undici's `ProxyAgent`, and the full list of breaking changes is in the V3 Migration Guide in the repository.
How do I handle errors from express-openid-connect safely?
Errors are handled by the default Express error handler, which in production does not include the stack trace or the error message. If you write your own error handler, the README warns against rendering the error message or the OAuth `error` and `error_description` properties without a templating engine that escapes them first.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/auth0-express-openid-connect)Was this useful?