openid-client: an OAuth 2 and OpenID Connect client for JavaScript runtimes
OAuth 2 / OpenID Connect Client API for JavaScript Runtimes
At a glance
- What is it?
- openid-client is a TypeScript library that wraps authorization server metadata discovery, the authorization code flow, DPoP, PAR, JARM and more behind a small set of functions. It is for developers who want protocol coverage without writing the protocol.
- Who is it for?
- Adopt openid-client when you need certified OpenID Connect and FAPI behaviour in Node.js, Deno, Bun, browsers or edge workers, and you are comfortable letting the library own PKCE, state, nonce and token exchange. Do not adopt it if you want a full framework with sessions, user storage and login pages, or if you need a grant the README does not list.
- 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 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What openid-client solves for JavaScript teams
OpenID Connect is a specification, not a library. Implementing it by hand means generating and storing a PKCE code_verifier, computing an S256 code_challenge, building an authorization URL with the right query parameters, validating the state value on the way back, exchanging the code at the token endpoint, and then deciding what to do when the authorization server publishes metadata you did not expect. Every one of those steps is a place to get a security detail wrong.
openid-client exists to remove that work. The README describes it as an OAuth 2 and OpenID Connect client API for JavaScript runtimes, and it is aimed at developers building the relying party side: the application that sends users to an authorization server and receives tokens back. It is not an authorization server, not an identity provider, and not a session framework. It gives you functions that produce and consume protocol messages.
The scope is broad. According to the feature list, the library covers authorization server metadata discovery, the authorization code flow profiled under OpenID Connect 1.0, OAuth 2.0, OAuth 2.1, FAPI 1.0 Advanced and FAPI 2.0, plus refresh token, device authorization, CIBA and client credentials grants. It also implements DPoP, token introspection and revocation, PAR, UserInfo and protected resource requests, issuer identification, JWT-secured introspection, JARM, JAR, JWT-secured UserInfo, dynamic client registration, and a Passport strategy.
That list is the reason to pick it over a thinner client. If your authorization server requires pushed authorization requests or a FAPI profile, most small libraries stop at the basic code flow and leave you to assemble the rest.
How discovery, PKCE and the token exchange fit together
The entry point is discovery. You give the library the authorization server's Issuer Identifier, a client identifier and a client secret, and it returns a Configuration object. That object carries the server metadata the library fetched, which is why later calls can build URLs and validate responses without you passing endpoint URLs around.
The authorization code flow then runs in two halves. On the way out, you generate a PKCE verifier with client.randomPKCECodeVerifier(), derive the challenge with client.calculatePKCECodeChallenge(), and pass redirect_uri, scope, code_challenge and code_challenge_method into client.buildAuthorizationUrl(). The result is a URL you redirect the user to. The README is explicit that the code_verifier and state must be stored in the end-user session so they can be recovered when the user comes back.
On the way back, client.authorizationCodeGrant() takes the configuration, the current URL of the callback request, the stored pkceCodeVerifier and the expectedState. It performs the token endpoint exchange and returns a TokenEndpointResponse. From there, client.fetchProtectedResource() sends the access token to a resource server.
One design detail is worth calling out. The README says PKCE is used regardless of whether the server advertises support, because use of PKCE is backwards compatible, and that random state is added only when config.serverMetadata().supportsPKCE() returns false. That is a deliberate trade-off: you always send a code challenge, and you only carry the extra state parameter when the server cannot be trusted to bind the code to your client. If you were expecting state to be mandatory on every request, this is the place to adjust your assumptions.
Installing openid-client and running the authorization code flow
The README states the package is distributed via npmjs.com, jsr.io and github.com. There is no separate installer or CLI. Install it with your package manager of choice; the package.json declares "type": "module" and the examples use ESM imports.
npm install openid-clientThe package exposes two entry points. The main one is imported as a namespace, and a second subpath serves the Passport strategy.
import * as client from 'openid-client'Configuration starts with discovery. The README's quick start shows a URL for the issuer, a client identifier and a client secret, and calls client.discovery() to obtain a Configuration.
let server!: URL // Authorization Server's Issuer Identifier
let clientId!: string // Client identifier at the Authorization Server
let clientSecret!: string // Client Secret
let config: client.Configuration = await client.discovery(
server,
clientId,
clientSecret,
)For a first real use, follow the Authorization Code Flow example. Generate the verifier and challenge, build the authorization URL, and redirect the user. The README notes that this is the flow to use when you want end-users to authorize or authenticate against a third-party API.
let code_verifier: string = client.randomPKCECodeVerifier()
let code_challenge: string =
await client.calculatePKCECodeChallenge(code_verifier)
let parameters: Record<string, string> = {
redirect_uri,
scope,
code_challenge,
code_challenge_method: 'S256',
}
let redirectTo: URL = client.buildAuthorizationUrl(config, parameters)When the user returns to your redirect_uri, consume the callback URL and exchange the code. The README's example passes the stored verifier and the expected state.
tokens = await client.authorizationCodeGrant(
config,
getCurrentUrl(),
{
pkceCodeVerifier: code_verifier,
expectedState: state,
},
)After that, tokens.access_token can be sent to a resource server with client.fetchProtectedResource(), which the README demonstrates against a URL such as https://rs.example.com/api. The repository also ships runnable files under examples/, including oauth.ts, oidc.ts, dpop.ts, jar.ts, jarm.ts, par.ts and passport.ts, with .diff files showing what each extension adds on top of the base example. Those diffs are the fastest way to see the cost of enabling PAR or DPoP.
Where openid-client stops and you have to start
The library is a protocol client, and the README is clear about the boundary. It gives you the authorization URL and the token response; it does not give you a session store, a login page, a user table or a logout flow. The PKCE verifier and state values have to be persisted somewhere between the redirect out and the redirect back, and the README places that responsibility on your application by saying they must be stored in the end-user session.
That is a real constraint in stateless deployments. If you run on Cloudflare Workers with no session storage of your own, you need somewhere to put the verifier before the user leaves your origin. The library does not provide it.
There is a second boundary around server support. The README's own code branches on config.serverMetadata().supportsPKCE(), which means the library expects to meet authorization servers that do not advertise PKCE. The extensions are the same story: PAR, JAR, JARM and DPoP only work if the authorization server implements them. Nothing in the client can compensate for a server that does not.
Finally, this is the wrong tool when your goal is a finished authentication product. If you want sign-in pages, password reset, account linking and an admin console, you are looking at a framework or a hosted identity service, not at a client library that returns token endpoint responses. The README's sponsor note points at one such product, but the library itself does not pretend to be one.
openid-client compared with oauth4webapi and Passport
The closest comparison in the search data is oauth4webapi, which is also maintained by the same author and targets the same runtimes with a similar functional style. The practical difference is scope. openid-client packages OpenID Connect on top of OAuth 2, including discovery, UserInfo, ID token handling and the FAPI profiles, and adds the Passport strategy subpath. oauth4webapi stays closer to the OAuth 2 and OAuth 2.1 side. If your authorization server is plain OAuth 2 and you never consume an ID token, the smaller surface is easier to reason about; if you need OpenID Connect certification profiles, openid-client is the one that carries them.
The other comparison people search for is openid-client against Passport. These are not alternatives at the same layer. Passport is an authentication middleware with a strategy interface, and openid-client ships a strategy for it, exported from the ./passport subpath and demonstrated in examples/passport.ts. Choosing openid-client does not exclude Passport; the strategy is how you connect the two. What you give up by going through Passport is direct control over the flow, because the middleware owns the redirect and callback handling.
A third option worth naming is better-auth, which appears in the search data. It is a framework-shaped authentication library, so the difference is the same as above: it owns sessions and user records, while openid-client owns protocol messages. Pick based on whether you want the library to hold your users or only to talk to the server that does.
Maintenance, licensing and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-14. Releases have been frequent, with v6.8.8 on 2026-09-05, v6.8.7 on 2026-08-20 and v6.8.6 on 2026-08-18. A CHANGELOG.md sits at the top level, and the repository carries .versionrc.json and .postchangelog.cjs, which suggests the release notes are generated as part of the version bump rather than written by hand. Anyone pinning a version should read that file for the release they pin.
The licence is MIT, declared in package.json and present as LICENSE.md. MIT permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. That is a permissive arrangement, but it says nothing about the authorization server you connect to, the terms of any hosted identity provider, or your own regulatory obligations. Those are separate questions and this article is not legal advice.
Upgrade cost is the part people underestimate. The search data includes a v5 to v6 question, which implies a breaking change between those majors, and the repository ships a docs/ directory and a typedoc.json, so the API reference is generated from source. The .postbump.cjs and .postchangelog.cjs scripts exist, but the README does not document a rollback or downgrade procedure. If you depend on a specific grant or extension, check the CHANGELOG.md and the examples/ diff files before moving majors, because the diff files show exactly which call sites the extension APIs touch.
Editorial conclusion
Adopt openid-client when you need certified OpenID Connect and FAPI behaviour in Node.js, Deno, Bun, browsers or edge workers, and you are comfortable letting the library own PKCE, state, nonce and token exchange. Do not adopt it if you want a full framework with sessions, user storage and login pages, or if you need a grant the README does not list. Before you commit, read the Authorization Code Flow example in examples/oidc.ts, confirm which grants your authorization server actually supports, and check the CHANGELOG.md entry for the version you pin.
Frequently asked questions
What is openid-client used for?
It is an OAuth 2 and OpenID Connect client API for JavaScript runtimes, used on the relying party side to talk to an authorization server. Its documented scope includes metadata discovery, the authorization code flow, refresh token, device authorization, CIBA and client credentials grants, plus extensions such as DPoP, PAR, JAR and JARM.
How do I use openid-client?
Install it from npm, jsr.io or GitHub, call client.discovery() with the issuer URL, client identifier and client secret to get a Configuration, then build an authorization URL with client.buildAuthorizationUrl() and exchange the returned code with client.authorizationCodeGrant(). The README notes that the PKCE code_verifier and state must be stored in the end-user session between the two steps.
How does openid-client compare with Passport?
They sit at different layers. openid-client is a protocol client, and it also ships a Passport strategy exported from the ./passport subpath. Using the strategy means Passport's middleware handles the redirect and callback, while direct use of the library leaves that flow in your own code.
How does openid-client compare with oauth4webapi?
Both target JavaScript runtimes and share an author. openid-client covers OpenID Connect on top of OAuth 2, including discovery, UserInfo, ID token handling and the FAPI profiles, while oauth4webapi stays closer to the OAuth 2 and OAuth 2.1 side. Choose based on whether you consume ID tokens.
What changed between openid-client v5 and v6?
The repository's own files do not describe the differences between the two majors. A CHANGELOG.md sits at the top level and generated API documentation lives under docs/, which are the places to check before moving between majors.
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/panva-openid-client)