openid/AppAuth-JS: the OpenID Foundation's client SDK for OAuth 2.0 in a public client
JavaScript client SDK for communicating with OAuth 2.0 and OpenID Connect providers.
At a glance
- What is it?
- AppAuth for JavaScript is an Apache-2.0 licensed TypeScript SDK for OAuth 2.0 and OpenID Connect in public clients, written against RFC 8252 for native applications and supporting PKCE plus arbitrary protocol extensions. The repository shows a modern build with Vite, the TypeScript compiler and the Google Closure Compiler, and a documentation section that no longer matches the test runner the project actually uses.
- Who is it for?
- Adopt AppAuth-JS when you are writing a public client, a browser application, a Node CLI or an Electron app, and you want the request and response shapes of the OAuth and OpenID Connect specifications rather than a wrapper around one provider's SDK. Do not adopt it for a confidential client, since the library is explicitly built for public clients and assumes it cannot hold a secret.
- Can I use it commercially?
- Yes. Apache-2.0 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 65 days 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Built for public clients, and it says so
AppAuth for JavaScript is a client SDK for public clients, in the sense the OAuth 2.0 specification gives that term, for communicating with OAuth 2.0 and OpenID Connect providers, following the best practice of RFC 8252, OAuth 2.0 for Native Apps.
That distinction is the design brief. A public client is an application that cannot keep a secret, a browser app, a desktop app, a CLI running on someone's laptop, and RFC 8252 exists because the assumptions the original OAuth 2.0 spec made about confidential clients do not hold in those environments. The library is designed for use in web apps, Node.js CLI applications, Chrome Apps and applications that use Electron or similar frameworks.
The project describes itself as striving to directly map the requests and responses of those specifications while following the idiomatic style of the implementation language. That is the right goal for a foundation maintained SDK, since the value is in not having to guess what the provider expects.
The package is `@openid/appauth`, version 1.4.0, authored by the OpenID Foundation and licensed under Apache-2.0. It is published as an ES module, with `main` at `built/src/index.js` and the type declarations at `built/src/index.d.ts`, and the published files include the `src/`, `app/` and `built/` directories, so the TypeScript sources ship alongside the compiled output.
PKCE, and an escape hatch for everything else
The library supports the PKCE extension to OAuth, RFC 7636, which was created to secure authorization codes in public clients when custom URI scheme redirects are used.
PKCE is the piece of machinery that makes the authorization code flow safe for an application that cannot hold a client secret, which is exactly the case this library targets. The public client sends a code verifier, the authorization server binds the challenge to the code it issues, and the token request has to present the verifier, so a code that is intercepted is useless without it.
The second capability is more of an attitude than a feature. The library is friendly to other extensions, standard or otherwise, with the ability to handle additional parameters in all protocol requests and responses. In the API that is the `extras` map on the request objects, so a provider specific parameter, a non standard prompt value, an additional scope or a vendor field can be carried without patching the library.
That matters in practice, because providers do extend these protocols, and a client SDK that cannot pass an extra parameter forces you to fork it. Here the extension point is a first class map on the request and response objects rather than a subclass you have to write.
The flow, in three code blocks
The README's example is a manual flow, where you perform your own token exchanges, and it is worth reading in full because it shows the shape of the API.
First, fetch the service configuration from the issuer:
AuthorizationServiceConfiguration.fetchFromIssuer(openIdConnectUrl)
.then(response => {
log('Fetched service configuration', response);
this.configuration = response;
this.showMessage('Completed fetching configuration');
})
.catch(error => {
log('Something bad happened', error);
this.showMessage(`Something bad happened ${error}`)
});Then make the authorization request, with a notifier, a redirect handler and a listener wired together:
this.notifier = new AuthorizationNotifier();
// uses a redirect flow
this.authorizationHandler = new RedirectRequestHandler();
// set notifier to deliver responses
this.authorizationHandler.setAuthorizationNotifier(this.notifier);
// set a listener to listen for authorization responses
this.notifier.setAuthorizationListener((request, response, error) => {
log('Authorization request complete ', request, response, error);
if (response) {
this.code = response.code;
this.showMessage(`Authorization Code ${response.code}`);
}
});The `extras` map appears right there in the request, with `prompt` and `access_type` set to `consent` and `offline`.
Finally, the token request, with the grant type chosen per case and the code verifier carried across from the authorization request:
this.tokenHandler = new BaseTokenRequestHandler();
let request: TokenRequest|null = null;
if (this.code) {
let extras: StringMap|undefined = undefined;
if (this.request && this.request.internal) {
extras = {};
extras['code_verifier'] = this.request.internal['code_verifier'];
}The `request.internal` line is the PKCE plumbing made visible. The verifier that the library generated for the authorization request is stashed on the request's internal state, and the token handler has to put it back into the token request's extras. That is one line of application code, and it is the line that makes the whole flow work against a provider that enforces PKCE.
Refresh tokens and the rest of the surface
The token request example also covers the other branch, where instead of a code the client holds a token response and makes a refresh request with `grant_type: GRANT_TYPE_REFRESH_TOKEN` and the refresh token from the previous response. So the same handler covers both the initial exchange and the ongoing renewal, which is the shape a long lived client needs.
The two handlers are named for what they do, `AuthorizationNotifier` and `BaseTokenRequestHandler`, and the redirect handler is the piece that differs by runtime. That is the one place where a public client has real work to do, since a browser can use a redirect URI that the server can come back to, while a Node CLI or an Electron app needs a custom URI scheme or a loopback, and RFC 8252 is the document that spells out the requirements for each case. The library provides `RedirectRequestHandler` for the flow the example uses, and the repository ships a Node CLI sample in `src/node_app`, with a separate electron sample published as a googlesamples repository.
The configuration and request types are plain objects with a constructor and named fields, `client_id`, `redirect_uri`, `scope`, `response_type`, `state`, `extras` for the authorization request, and `grant_type`, `code`, `refresh_token` and `extras` for the token request, with `state` settable to `undefined` so a client can opt out of it deliberately rather than by omission.
A modern build, and a documentation page that has drifted
The build tooling is current, and it is worth a look because the scripts show what the project expects of a contributor.
Compilation is `tsc` into a `built/` directory, with a `watch` script for continuous feedback and a `clean` script that removes `built/`. The example applications are built with Vite, `vite` for the dev server and `vite build app` for the bundle. The minified bundle is produced with the Google Closure Compiler, run as a Java jar from `node_modules`, and there is a `gzipSize` script that gzips the minified bundle and counts the bytes, which is a size budget enforced as a script rather than a claim in a document. Formatting is done with `clang-format`, invoked as `clang-format -i -style=file` over the TypeScript sources, and the repository has a `.clang-format` file and an `.editorconfig`.
Here is the drift worth flagging. The development section of the README describes the test setup as `npm test` provisioning the Karma test runner to run all unit tests, with all tests written using Jasmine, and tells you to click the Debug button in the Karma test runner to set breakpoints. The `package.json` in the repository says something different, since the `test` script is `node_modules/.bin/vitest run`, and the tree has a `vitest.config.ts` and a `vite.config.ts`.
So the project has moved from Karma and Jasmine to Vitest, and the README was not updated. That is a small thing, but it is exactly the kind of detail that sends a new contributor down a wrong path, and it is a reminder to read the manifest rather than the prose when the two disagree.
The rest of the setup guidance is ordinary: install the latest Node, with the Node Version Manager highly recommended, and the repository has an `.nvmrc` pinning the recommended version. Then clone, go to the folder with the `package.json`, and run `npm install`.
Where AppAuth-JS is the wrong choice
The first wrong case is a confidential client. The library is explicitly built for public clients, which means it assumes there is no client secret to protect and that the authorisation code has to be protected some other way, namely with PKCE. If you are writing a server side service that can hold a secret, this library is the wrong shape, because a browser oriented SDK is not where a confidential client belongs.
The second is a single provider with a well documented SDK of its own. If you only ever talk to one identity provider and that provider publishes its own client library, that library will know its own quirks, its own error shapes and its own token refresh rules, and adding a general purpose SDK on top is a layer you have to debug through.
The third is a runtime whose redirect handling does not match the library's model. The whole flow depends on getting the browser back to your application with the code, and the mechanisms differ between a web server, a Node CLI, a Chrome App and an Electron main process. The library covers the cases the samples show, and reading which one applies to you is the first task rather than the last.
The fourth is an application that needs a flow this library does not implement. The examples cover the authorization code flow and the refresh flow, and the documentation is focused on the manual exchange. A device flow for input constrained devices, or an implicit flow, are not demonstrated in what is here, and a foundation SDK that does not implement a flow is better left alone than forced.
On the state of the project, the last push to the repository was on 2026-08-18, the repository is not archived, and there are no GitHub releases, so the version history is the package registry's rather than the repository's. The default branch is `master`, the licence is Apache-2.0, and the project is maintained under the OpenID Foundation, which is the reassurance that matters most for a security component.
Editorial conclusion
Adopt AppAuth-JS when you are writing a public client, a browser application, a Node CLI or an Electron app, and you want the request and response shapes of the OAuth and OpenID Connect specifications rather than a wrapper around one provider's SDK. Do not adopt it for a confidential client, since the library is explicitly built for public clients and assumes it cannot hold a secret. Verify first that the redirect handling in the runtime you are using matches what the library expects, since a browser, a Node CLI and an Electron app each need a different redirect mechanism, and that the version of the library you pick is the one whose behaviour you have read, because the repository publishes no GitHub releases to compare against.
Frequently asked questions
What is AppAuth-JS and which applications is it for?
It is the OpenID Foundation's client SDK for public clients communicating with OAuth 2.0 and OpenID Connect providers, following RFC 8252 for native applications. It is designed for web apps, Node.js CLI applications, Chrome Apps and applications built on Electron or similar frameworks.
Does AppAuth-JS support PKCE?
Yes. It supports the PKCE extension to OAuth, RFC 7636, which secures authorization codes in public clients that use custom URI scheme redirects. The code verifier is carried on the request's internal state and has to be put into the token request's extras.
How do I add provider specific parameters to an AppAuth-JS request?
Through the `extras` map on the request objects, which the library accepts in all protocol requests and responses. The example sets `prompt` and `access_type` that way, and the project describes the library as friendly to other extensions, standard or otherwise.
Which test runner does AppAuth-JS use?
Vitest. The `package.json` test script is `node_modules/.bin/vitest run` and the repository has a `vitest.config.ts`, even though the README's development section still describes Karma and Jasmine. The manifest is the current source.
How is the AppAuth-JS example bundle built and sized?
With Vite for the example applications and the Google Closure Compiler for the minified bundle, run as a Java jar from `node_modules`. A `gzipSize` script gzips the minified bundle and counts the bytes, and the format script uses `clang-format` with the repository's `.clang-format` file.
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/openid-appauth-js)Was this useful?