# oauth4webapi: a low-level OAuth 2 and OIDC client for JavaScript runtimes

> oauth4webapi is a dependency-free TypeScript library that implements the fiddly parts of OAuth 2, OAuth 2.1 and OpenID Connect for both browsers and server runtimes, including PKCE, DPoP, PAR, JARM, JAR, CIBA and dynamic client registration, and it is OpenID certified for the Basic and FAPI relying party profiles. It is deliberately low-level: it gives you the protocol machinery and none of the session handling, token storage or UI a higher level SDK would add.

**panva/oauth4webapi** — Low-Level OAuth 2 / OpenID Connect Client API for JavaScript Runtimes

- Repository: https://github.com/panva/oauth4webapi
- Stars: 791 · Forks: 74
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/panva-oauth4webapi

## Low-level on purpose

The title of the project says it plainly: a low-level OAuth 2 and OpenID Connect client API for JavaScript runtimes. The README states the goal as promoting secure and up-to-date best practices while using only the capabilities common to both browser and non-browser JavaScript runtimes.

That last clause is the design constraint that shapes everything. A library that works in a browser cannot depend on Node's crypto module or on a file system, and a library that runs on an edge runtime cannot depend on anything that requires a long-lived process. So oauth4webapi restricts itself to Web API globals and standard built-in objects, and the supported runtime list follows directly: browsers, Bun, Cloudflare Workers, Deno, Electron and Node.js, with Node.js v20.x as the stated baseline.

For the user, the pitch is correctness. OAuth has accumulated a lot of optional security machinery over the years, PKCE, DPoP, Pushed Authorization Requests, JWT-secured responses, and each of those has a spec, an implementation quirk and a way to get it subtly wrong. The library collects the routines so you do not have to reimplement them per project.

What you give up is the convenience layer. There is no session store, no user database, no automatic refresh, no sign-in widget, and no opinion about where your tokens live. You get the protocol calls and the validation rules, and you wire the rest yourself. Whether that trade is right depends entirely on whether you are building a library, an agent, an edge service or a bespoke client for one specific authorization server.

## The protocol surface it implements

The features list in the README is the best catalogue available, and it is a long one.

Discovery comes first: Authorization Server Metadata discovery and Resource Server Metadata discovery. Then the flows. The Authorization Code Flow is profiled under OpenID Connect 1.0, OAuth 2.0, OAuth 2.1 and FAPI 2.0, with PKCE. Alongside it are the Refresh Token, Device Authorization, Client-Initiated Backchannel Authentication and Client Credentials grants.

The security extensions are the more distinctive part. Demonstrating Proof-of-Possession at the Application Layer, that is DPoP, is implemented, as are Pushed Authorization Requests and JWT Secured Introspection, Response Mode with JARM, Authorization Request with JAR, and UserInfo. Dynamic Client Registration is there too, so a client can register itself with an authorization server rather than being provisioned by hand. The list also covers Token Introspection and Revocation, UserInfo and protected resource requests, Authorization Server Issuer Identification, and validating incoming JWT access tokens.

Issuer identification is the one that quietly matters most in production. If a client accepts tokens without checking which issuer minted them, a token from the wrong server can be accepted by accident. Having the routine available is one thing, using it is another, but the library makes it possible without writing JWKS handling from scratch.

The certification note adds weight to the list. The README states that Filip Skokan has certified that this software conforms to the Basic, FAPI 1.0 and FAPI 2.0 Relying Party Conformance Profiles of the OpenID Connect protocol, which means the implementation has been run against the official conformance suite rather than only against its author's expectations.

## Installing it and getting the import right

The package is published in four places, and the choice matters depending on your runtime and your build. It is distributed through npmjs.com, jsr.io, jsdelivr.com and GitHub itself. The package metadata shows version 3.8.8, with sideEffects set to false and type set to module.

The import is a single namespace import, and the README notes that it is an ESM import:

```js
import * as oauth from 'oauth4webapi'
```

CommonJS is possible but conditional. The footnote in the README says that CJS style require of the package is possible in Node.js versions where the require(esm) feature is enabled by default, that is ^20.19.0 || ^22.12.0 || >= 23.0.0. Below those versions, a CommonJS build cannot require it directly.

The published file list is deliberately tiny, three entries:

```json
  "name": "oauth4webapi",
  "version": "3.8.8",
  "sideEffects": false,
  "type": "module",
```

The exports map points the package root at the built index.js with its type declarations alongside, and the files array ships only build/index.js, its source map and the declaration file. Nothing else is published, so there is no risk of a consumer accidentally importing internal modules.

The runtime list is broader than npm alone suggests. Since the package is also on jsr.io, it is consumable from Deno without an npm client, and jsdelivr serves it for browser use. The repository itself contains a jsr.json alongside mod.ts, which is what makes the JSR publication work, and the build script ends with a jsr publish dry run, so a broken JSR release fails the build rather than the registry.

## Examples shipped as diffs against a base flow

The examples directory is the most useful thing in the repository for learning how the library is meant to be used, and its structure is unusual.

Each capability has a source file and, in most cases, a diff file. The OAuth 2.0 Authorization Code Flow lives in examples/oauth.ts, and the OpenID Connect version in examples/oidc.ts with examples/oidc.diff alongside it. The extension examples follow the same pattern: DPoP in dpop.ts with dpop.diff, JWT Secured Authorization Request in jar.ts with jar.diff, JARM in jarm.ts with jarm.diff, Pushed Authorization Request in par.ts with par.diff.

The same holds for client authentication, where the variants are a client secret in the HTTP Authorization header, a client secret in the HTTP body, private key JWT client authentication and the public client case. For grants there are examples for client credentials, CIBA, the device authorization grant and the refresh token grant. The FAPI set includes FAPI 1.0 Advanced, FAPI 2.0 Security Profile and FAPI 2.0 Message Signing.

What the diffs give you is a reviewable delta. Rather than reading two long files and working out what changed, you see exactly which calls a given extension adds to a working baseline, which is close to how a reviewer reads a pull request. The update script examples/.update-diffs.sh keeps those diffs in step with their sources.

The examples are also compiled as part of the build, with separate TypeScript projects for test, examples, conformance and tap, so a broken example fails the build. That is a nice property: the documentation cannot silently rot.

## Testing against the conformance suite

The repository contains a conformance directory and a playwright.config.ts, and the package scripts show how the pieces fit together.

There is a conformance script that sources a flags file and runs ava with the conformance configuration, and ava.config.mjs sits at the root as the test runner configuration. The Playwright configuration points at the conformance suite as well, which tells you the conformance tests drive a browser, because an OpenID conformance suite for a relying party runs your client in a real browser session and checks that the flows behave.

The documentation tooling is separate. There is a docs script that runs patch-package and then typedoc, and a docs-check script that executes check-examples.sh, which presumably verifies that the code samples in the generated documentation still match the examples in the repository. The typedoc.json file holds the typedoc configuration, and the docs are published from the docs directory with the API reference in docs/README.md.

Packaging gets its own check. There is a check:packaging script running publint in strict mode together with a tools/attw-check.js script, which is the standard way to verify that an ESM package exposes the right export conditions and does not break consumers who resolve it differently.

Put together, this is a mature release engineering setup: format with prettier, build with tsc across four project configurations, publish a JSR dry run, lint the package for correctness, check the docs against the examples, and run the conformance suite in a browser. For a library with zero dependencies, the amount of machinery around the code is the interesting part.

## Where a low-level client stops being the right tool

Several limits follow directly from the design, and it is better to name them than to discover them.

The first is scope. There is no session management, no token storage, no refresh orchestration and no UI. If your requirement is a login button and a signed-in state, this library leaves all of it to you, and a higher-level SDK would deliver it in a fraction of the time. The library is for people who need the protocol details and are willing to own the rest.

The second is that the API surface is wide. The features list covers discovery, four flows, four extensions, client registration and token validation, and each of those has its own conventions and failure modes. A team adopting it for a single authorization server with a single flow will find a lot of documented surface they do not need.

The third is runtime baseline. Node.js v20.x is required, and CommonJS consumers need a version where require(esm) is on by default, which excludes older Node releases and older bundler setups. On the browser side you need the Web API globals the library uses, so an exotic runtime without fetch would be out.

The fourth is concentration of maintenance. The package metadata names a single author, Filip Skokan, and the sponsor section asks for community funding to continue maintaining the module. A single-maintainer library with no dependencies is a different risk profile from one with a broad team, and that is worth weighing before an organisation standardises on it.

The fifth is that a certified conformance profile is not the same as a tested integration. Certification covers the Basic, FAPI 1.0 and FAPI 2.0 relying party profiles. Anything outside those, in particular an authorization server with unusual quirks, still needs your own testing.

## Licence, versioning and who maintains it

The licence is MIT, with the file at LICENSE.md and the package metadata carrying the same value, so the module can be vendored, modified and shipped inside a commercial product without copyleft obligations.

Maintenance is active and the release history is regular. The repository is not archived and the last push was on 2026-10-13. The listed releases are v3.8.8 on 2026-09-05, v3.8.7 on 2026-08-11 and v3.8.6 on 2026-04-27. The README's supported versions table shows the v3.x branch receiving security fixes, other bug fixes and new features, which tells you the 3.x line is the one to follow.

The versioning tooling is in the repository root and is unusual enough to notice. There is a .versionrc.json for the version bump configuration, a .postbump.cjs and a .postchangelog.cjs that run after a bump, and a CHANGELOG.md. The changelog is generated rather than written by hand, which is why the release notes read consistently.

There are also .node_flags.sh and .electron_flags.sh files, which are sourced by the test and conformance scripts to pass the right runtime flags for Node.js and for Electron respectively, and a patches directory used with patch-package during the docs build.

On the governance side the repository carries a SECURITY.md with a linked security policy, a CODE_OF_CONDUCT.md and a CONTRIBUTING.md. The sponsor directory and the sponsorship section in the README show the financial side: the project asks for community sponsorship to keep it maintained, and lists an Auth0 by Okta sponsor alongside it.

So the picture is a single-maintainer, dependency-free, standards-certified library with a regular release train, an open security policy and a funding model that depends on sponsorship. For the kind of code it is, one long-lived, well-funded module is often exactly what you want.

## Conclusion

Adopt oauth4webapi if you are writing a client for a modern authorization server and want the protocol details handled correctly, in one place, without pulling a full SDK into a browser bundle. Do not adopt it if you want a sign-in widget, a session store or automatic token refresh, because none of that is here and you will have to write it. Before you start, confirm your runtime meets the baseline of Node.js v20.x with the fetch API, read the examples folder where each flow ships as a source file plus a diff, and check the certification status of your target profile in the conformance suite, since only the Basic, FAPI 1.0 and FAPI 2.0 relying party profiles are certified.

## FAQ

### What is oauth4webapi and does it handle sessions for me?

It is a low-level OAuth 2 and OpenID Connect client API for JavaScript runtimes, providing the protocol routines for OAuth 2.0, OAuth 2.1, FAPI 2.0 and OpenID Connect where applicable. It does not provide session management, token storage or a sign-in interface.

### How do I import oauth4webapi in a JavaScript project?

As an ESM namespace import, import * as oauth from 'oauth4webapi'. CommonJS require is only possible in Node.js versions where require(esm) is enabled by default, which the README gives as ^20.19.0 || ^22.12.0 || >= 23.0.0, and Node.js v20.x is the baseline runtime.

### Which OAuth extensions does oauth4webapi implement?

The feature list covers PKCE, DPoP, Pushed Authorization Requests, JARM, JAR, JWT Secured Introspection, Client-Initiated Backchannel Authentication, the Device Authorization grant, Dynamic Client Registration, Token Introspection and Revocation, and validation of incoming JWT access tokens, plus authorization server and resource server metadata discovery.

### Is oauth4webapi certified for OpenID Connect conformance?

The README states that Filip Skokan has certified that the software conforms to the Basic, FAPI 1.0 and FAPI 2.0 Relying Party Conformance Profiles of the OpenID Connect protocol. The repository also carries a conformance suite and a Playwright configuration to run it.

### Does oauth4webapi have dependencies and what licence is it under?

It has no dependencies and exports tree-shakeable ESM, with sideEffects set to false in the package metadata. The licence is MIT, the current version is 3.8.8, the last push was on 2026-10-13, and the package is published to npm, jsr.io, jsdelivr and GitHub.

## Sources

- [Issues](https://github.com/panva/oauth4webapi/issues)
- [License: MIT](https://github.com/panva/oauth4webapi/blob/main/LICENSE)
- [panva/oauth4webapi on GitHub](https://github.com/panva/oauth4webapi)
- [README](https://github.com/panva/oauth4webapi/blob/main/README.md)
- [Releases](https://github.com/panva/oauth4webapi/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/panva-oauth4webapi
