包详细信息

@badgateway/oauth2-client

badgateway469.5kMIT3.2.0

OAuth2 client for browsers and Node.js. Tiny footprint, PKCE support

fetch, oauth2, pkce, security

自述文件

OAuth2 client for Node and browsers

This package contains an OAuth2 client. It aims to be a fully-featured OAuth2 utility library, for Node.js, Browsers and written in Typescript.

This OAuth2 client is only 4KB gzipped, it has 0 dependencies and relies on modern APIs like fetch() and Web Crypto which are built-in since Node 18.

Highlights

  • 16KB minified (5KB gzipped).
  • No dependencies.
  • authorization_code grant with optional PKCE support.
  • password and client_credentials grant.
  • a fetch() wrapper that automatically adds Bearer tokens and refreshes them.
  • OAuth2 endpoint discovery via the Server metadata document (RFC8414).
  • OAuth2 Token Introspection (RFC7662).
  • Resource Indicators for OAuth 2.0 (RFC8707).
  • OAuth2 Token Revocation (RFC7009).
  • OAuth 2.0 Multiple Response Type Encoding Practices

Installation

npm i @badgateway/oauth2-client

Usage

To get started, set up the Client class.

import { OAuth2Client } from '@badgateway/oauth2-client';

const client = new OAuth2Client({

  // The base URI of your OAuth2 server
  server: 'https://my-auth-server/',

  // OAuth2 client id
  clientId: '...',

  // OAuth2 client secret. Only required for 'client_credentials', 'password'
  // flows. Don't specify this in insecure contexts, such as a browser using
  // the authorization_code flow.
  clientSecret: '...',


  // The following URIs are all optional. If they are not specified, we will
  // attempt to discover them using the oauth2 discovery document.
  // If your server doesn't have support this, you may need to specify these.
  // you may use relative URIs for any of these.


  // Token endpoint. Most flows need this.
  // If not specified we'll use the information for the discovery document
  // first, and otherwise default to /token
  tokenEndpoint: '/token',

  // Authorization endpoint.
  //
  // You only need this to generate URLs for authorization_code flows.
  // If not specified we'll use the information for the discovery document
  // first, and otherwise default to /authorize
  authorizationEndpoint: '/authorize',

  // OAuth2 Metadata discovery endpoint.
  //
  // This document is used to determine various server features.
  // If not specified, we assume it's on /.well-known/oauth2-authorization-server
  discoveryEndpoint: '/.well-known/oauth2-authorization-server',

});

Tokens

Many functions use or return a 'OAuth2Token' type. This type has the following shape:

export type OAuth2Token = {
  accessToken: string;
  refreshToken: string | null;

  /**
   * When the Access Token expires.
   *
   * This is expressed as a unix timestamp in milliseconds.
   */
  expiresAt: number | null;


  /**
   * If the server returned an OpenID Connect ID token, it will be stored here.
   */
  idToken?: string;

};

client_credentials grant.

const token = await client.clientCredentials();

Refreshing tokens

const newToken = await client.refreshToken(oldToken);

password grant:

const token = await client.password({
  username: '..',
  password: '..',
});

authorization_code

The authorization_code flow is the flow for browser-based applications, and roughly consists of 3 major steps:

  1. Redirect the user to an authorization endpoint, where they log in.
  2. Authorization endpoint redirects back to app with a 'code' query parameter.
  3. The code is exchanged for a access and refresh token.

This library provides support for these steps, but there's no requirement to use its functionality as the system is mostly stateless.

import { OAuth2Client, generateCodeVerifier } from 'client';

const client = new OAuth2Client({
  server: 'https://authserver.example/',
  clientId: '...',

  // Note, if urls cannot be auto-detected, also specify these:
  tokenEndpoint: '/token',
  authorizationEndpoint: '/authorize',
});

Redirecting the user to the authorization server

/**
 * This generates a security code that must be passed to the various steps.
 * This is used for 'PKCE' which is an advanced security feature.
 *
 * It doesn't break servers that don't support it, but it makes servers that
 * so support it more secure.
 *
 * It's optional to pass this, but recommended.
 */
const codeVerifier = await generateCodeVerifier();

// In a browser this might work as follows:
document.location = await client.authorizationCode.getAuthorizeUri({

  // URL in the app that the user should get redirected to after authenticating
  redirectUri: 'https://my-app.example/',

  // Optional string that can be sent along to the auth server. This value will
  // be sent along with the redirect back to the app verbatim.
  state: 'some-string',

  codeVerifier,

  scope: ['scope1', 'scope2'],

});

Handling the redirect back to the app and obtain token

const oauth2Token = await client.authorizationCode.getTokenFromCodeRedirect(
  document.location,
  {
    /**
     * The redirect URI is not actually used for any redirects, but MUST be the
     * same as what you passed earlier to "authorizationCode"
     */
    redirectUri: 'https://my-app.example/',

    /**
     * This is optional, but if it's passed then it also MUST be the same as
     * what you passed in the first step.
     *
     * If set, it will verify that the server sent the exact same state back.
     */
    state: 'some-string',

    codeVerifier,

  }
);

Fetch Wrapper

When using an OAuth2-protected API, typically you will need to obtain an Access token, and then add this token to each request using an Authorization: Bearer header.

Because access tokens have a limited lifetime, and occasionally needs to be refreshed this is a bunch of potential plumbing.

To make this easier, this library has a 'fetch wrapper'. This is effectively just like a regular fetch function, except it automatically adds the header and will automatically refresh tokens when needed.

Usage:

import { OAuth2Client, OAuth2Fetch } from '@badgateway/oauth2-client';

const client = new OAuth2Client({
  server: 'https://my-auth-server',
  clientId: 'my-client-id'
});


const fetchWrapper = new OAuth2Fetch({
  client: client,

  /**
   * You are responsible for implementing this function.
   * it's purpose is to supply the 'initial' oauth2 token.
   */
  getNewToken: async () => {

    // Example
    return client.clientCredentials();

    // Another example
    return client.authorizationCode.getToken({
      code: '..',
      redirectUri: '..',
    });

    // You can return null to fail the process. You may want to do this
    // when a user needs to be redirected back to the authorization_code
    // endpoints.
    return null;

  },

  /**
   * Optional. This will be called for any fatal authentication errors.
   */
  onError: (err) => {
    // err is of type Error
  }

});

After set up, you can just call fetch on the new object to call your API, and the library will ensure there's always a Bearer header.

const response = fetchWrapper.fetch('https://my-api', {
  method: 'POST',
  body: 'Hello world'
});

Storing tokens for later use with FetchWrapper

To keep a user logged in between sessions, you may want to avoid full reauthentication. To do this, you'll need to store authentication token somewhere.

The fetch wrapper has 2 functions to help with this:


const fetchWrapper = new OAuth2Fetch({
  client: client,

  getNewToken: async () => {

    // See above!

  },

  /**
   * This function is called whenever the active token changes. Using this is
   * optional, but it may be used to (for example) put the token in off-line
   * storage for later usage.
   */
  storeToken: (token) => {
    document.localStorage.setItem('token-store', JSON.stringify(token));
  },

  /**
   * Also an optional feature. Implement this if you want the wrapper to try a
   * stored token before attempting a full re-authentication.
   *
   * This function may be async. Return null if there was no token.
   */
  getStoredToken: () => {
    const token = document.localStorage.getItem('token-store');
    if (token) return JSON.parse(token);
    return null;
  }

});

Fetch Middleware function

It might be preferable to use this library as a more traditional 'middleware'.

The OAuth2Fetch object also exposes a mw function that returns a middleware for fetch.

const mw = oauth2.mw();
const response = mw(
  myRequest,
  req => fetch(req)
);

This syntax looks a bit wild if you're not used to building middlewares, but this effectively allows you to 'decorate' existing request libraries with functionality from this oauth2 library.

A real example using the Ketting library:

import { Client } from 'ketting';
import { OAuth2Client, OAuth2Fetch } from '@badgateway/oauth2-client';

/**
 * Create the oauth2 client
 */
const oauth2Client = new OAuth2Client({
  server: 'https://my-auth.example',
  clientId: 'foo',
});

/**
 * Create the 'fetch helper'
 */
const oauth2Fetch = new OAuth2Fetch({
  client: oauth2Client,
});

/**
 * Add the middleware to Ketting
 */
const ketting = new Client('http://api-root');
ketting.use(oauth2Fetch.mw());

Introspection

Introspection (RFC7662) lets you find more information about a token, such as whether it's valid, which user it belongs to, which oauth2 client was used to generate it, etc.

To be able to use it, your authorization server must have support for the introspection endpoint. It's location will be automatically detected using the Metadata discovery document.

import { OAuth2Client } from '@badgateway/oauth2-client';

const client = new Client({
  server: 'https://auth-server.example/',

  clientId: '...',

  /**
   * Some servers require OAuth2 clientId/clientSecret to be passed.
   * If they require it, specify it. If not it's fine to omit.
   */
  clientSecret: '...',

});

// Get a token
const token = client.clientCredentials();

// Introspect!
console.log(client.introspect(token));

OAuth2 client_id and client_secret encoding

OAuth2 allows users to encode the client_id and client_secret either in a Authorization: Basic header or in the POST request body.

Real-world OAuth2 servers may support one or the other, or both. The OAuth2 spec requires that servers support the Authorization header, and don't recommend using the body.

By default, this library will use the Authorization header. OAuth2 also requires that clients percent-encode the client_id and client_secret, but in practice many popular servers break if you do this. By default this library will not percent encode any characters except the : character.

You can change this behavior using the authenticatioMethod flag:

const client = new OAuth2Client({
  server: 'https://auth-server.example/',
  clientId: '...',
  clientSecret: '...',
  authenticationMethod: 'client_secret_post', // encode in POST body 
});

The following 3 values are currently supported:

  • client_secret_post - Encode in POST body
  • client_secret_basic - Encode in Authorization header using the strict standard rules.
  • client_secret_basic_interop - Encode in Authorization header using less strict rules. This is the default and more likely to work with popular servers (at least some Google and Ebay APIs want this).

The current OAuth 2.1 draft switches the recommendation to use client_secret_post by default instead. When that document stabilizes and gets released, this library will also switch to use client_secret_post by default in a major release.

If your OAuth2 server supports POST, we recommend you use client_secret_post as this is more likely to work without a hitch.

If you configured the client using the OAuth2 discovery document, and the server indicates it prefers client_secret_basic we will also default to the strict form.

Support for older Node versions

This package works out of the box with modern browsers and Node 18.

For Node 16 and below, use a 2.x versions of this package and add polyfills. The README.md for the 2.x version of this package contains more information on the exact steps for older Node versions.

更新日志

Changelog

3.2.0 (2025-04-23)

  • 180: The browser build was failing to work for vite and next.js users since

    v3. This was probably due to the switch to ESM. Instead of trying to fix this problem in Webpack, this library has switched to vite for the minified browser build instead.

3.1.1 (2025-04-17)

  • Ignore auhtentication methods from Discovery document we don't support.

3.1.0 (2025-04-14)

  • 181: Revert back to percent-encoding of the Authorization: Basic header.

    Even though this was more correct from a standards perspective, this is causing interopability problems with popular real-world OAuth2 servers. If you need strict encoding, you can opt-in using the authorizationMethod option. We recommend using client_secret_post if it's possible with your server. See README.md for more information on this behaviour and what the possible options are.

3.0.0 (2025-03-06)

  • Dropped support for Node 14 and 16.
  • Full conversion to ESM.
  • Support for the OpenID Connect id_token. If a server returns it, we expose it as idToken. This is a JWT and would require parsing by a JWT library to get access to its information. (@drev74, @redguardtoo).
  • 171: client_id and client_secret are now percent-encoded with the most

    strict rules as specified by RFC 6749. We weren't doing any percent/urlencoding before. This is a a BC break if your secret used special characters, and the server you're talking is not compliant with the OAuth2 spec itself (@p2004a, @panva).
  • Migrated the test suite from Mocha and Chai to node:test and node:assert (@Zen-cronic).
  • Package now uses 'erasableSyntaxOnly' flag with Typescript, so it can be used with node --experimental-strip-types.

2.4.2 (2024-09-14)

  • 161: Re-use old refresh_token if no new one was issued after a refresh.

2.4.1 (2024-08-22)

  • 151: Add 'Accept' header on token requests to fix a Github compatibility

    issue.
  • 151: Throw error when we get an invalid reply from a token endpoint.

2.4.0 (2024-07-27)

  • More robust error handling. When an error is emitted, you now give you access to the emitted HTTP Response and response body.
  • Support for response_mode=fragment in the authorization_code flow.

2.3.0 (2024-02-03)

  • Fix for #128: If there's no secret, we should never use Basic auth to encode the client_id.
  • Support for the resource parameter from RFC 8707.
  • Add support for scope parameter to refresh().
  • Support for RFC 7009, Token Revocation (@adambom).

2.2.4 (2023-09-05)

  • Added extraParams option to getAuthorizeUri, allowing users to add non-standard arguments to the authorization URI for servers that require this. (@pks1989)

2.2.3 (2023-08-03)

  • Moved the tokenResponseToOAuth2Token function inside the OAuth2Client class, allowing users to override the parsing logic more easily.

2.2.2 (2023-07-28)

  • 111 Some documentation fixes.

  • 110: Fix race condition with getStoredToken and calling fetch()

    immediately after constructing FetchWrapper.

2.2.1 (2023-07-07)

  • 15: Fix for 'TypeError: Failed to execute 'fetch' on 'Window': Illegal

    invocation at t.OAuth2Client.request'.

2.2.0 (2023-04-26)

  • Add an option to override which "fetch" implementation is used. (@bradjones1)

2.1.1 (2023-04-23)

  • Re-release. Previous build had an error.

2.1.0 (2023-04-20)

  • Allow users to provide non-standard properties to client_credentials token requests via an extraParams property. This is necessary to support vendors like Auth0 and Kinde which both require an audience parameter. (@South-Paw)
  • Sending client_id and client_secret in POST request body is now optionally supported. By default the credentials will still be sent in the Authorization header, but users can opt-in to using the body. The authentication method will also be discovered if an OAuth2 or OpenID discovery document is used. (@parkerduckworth)
  • The fetchWrapper now has an option to disable auto-refreshing tokens. (@bradjones1)
  • Bug fix: If a 'state' parameter was not used in authorization_code, it should not be required in the redirect.
  • Tested with Node 20.

2.0.18 (2023-04-13)

  • Didn't run make build before the last release, which causes some files in the dist/ directory to be out of date.

2.0.17 (2022-10-02)

  • Correctly pass 'scope' to authorization_code redirects.

2.0.16 (2022-07-22)

  • It was not possible to generate the URL to the authorization endpoint with PKCE using Node, due to depending on a global crypto object. This is fixed with fallbacks all the way back to Node 14.

2.0.15 (2022-07-07)

  • 70: Sending the client secret is now supported with the authorization_code

    flow.

2.0.14 (2022-06-23)

  • Re-release, to publish on Github packages.

2.0.13 (2022-06-19)

  • Fixed some docs.

2.0.12 (2022-06-19)

  • First stable v2 release!
  • Renamed this package from fetch-mw-oauth2 to @badgateway/oauth2-client.
  • 59: Scope support for authorization_code flow.

2.0.11 (2022-06-17)

  • Released with alpha tag.
  • Re-published

2.0.10 (2022-05-10)

  • Released with alpha tag.
  • Tested on Node 14, 16.
  • Added polyfills for these node versions (see README).
  • generateCodeVerifier is now async to support Node 14.

2.0.9 (2022-04-26)

  • Released with alpha tag.
  • Set Content-Type to application/x-www-form-urlencoded.

2.0.8 (2022-04-26)

  • Released with alpha tag.
  • Changing the authorization_code signature again. It's a bit hard to come up with a create signature for this, especially because there's multiple steps in the process, and some information has to survive these steps.

2.0.7 (2022-04-26)

  • Released with alpha tag.
  • Re-release (broken build).

2.0.6 (2022-04-26)

  • Released with alpha tag.
  • Removed redundant parameters.
  • authorization_code should now also work correctly without PKCE.
  • Removed some redundant arguments.

2.0.5 (2022-04-25)

  • Released with alpha tag.
  • PKCE support.

2.0.4 (2022-04-20)

  • Released with alpha tag.
  • remove fetchMw and add mw(). mw() now returns a middleware function.

2.0.3 (2022-04-19)

  • Released with alpha tag.
  • Export OAuth2AuthorizationCodeClient
  • Client.authorizationCode() should not have been async.

2.0.2 (2022-04-19)

  • Released with alpha tag.
  • Fix format for introspect() function.

2.0.1 (2022-04-19)

  • Released with alpha tag.
  • Fix introspection HTTP method name.

2.0.0 (2022-04-19)

The 2.0 version of this library is a complete rewrite. The original scope of this library was to provide a wrapper around fetch() to add a Bearer token and refresh this token under the hood, but it has now evolved into a full-featured modern OAuth2 library. The existing 'fetch wrapper' still exists, but it's not merely one of the features this package offers. The API has changes, and while I think it shouldn't be difficult to migrate, v2 offers no backwards compatibility so some rewrites will be required. New features include:

  • Complete support for the authorization_code flow, including generating redirect urls and parsing query parameters after redirect.
  • Support for OAuth2 endpoint discovery, using the OAuth2 Authorization Server Metadata document. If your server supports it, just give the library a URL and it will figure out the rest. [RFC8414][2].
  • Support for OAuth2 token introspection ([RFC7662][3]).
  • Generally a better abstraction of the OAuth2 framework.
  • Released with alpha tag.

1.0.0 (2021-10-28)

  • Dropped support for Node 10.
  • Fixed #45: Call onAuthError when a refresh fails.
  • Replaced awesome-typescript-loader with ts-loader for Webpack builds. the former appears unmaintained.
  • Switched from Travis CI to Github Actions.

0.7.7 (2021-02-22)

  • Last version did not correctly build it's files.

0.7.6 (2021-02-22)

  • Better error handling when the response we got was not a standard OAuth2 error response body + adding information for when the Basic credentials were wrong.
  • This fixes the bug when fetch-mw-oauth2 says there's an 'undefined' error.

0.7.5 (2020-12-03)

  • Fixing a few broken links in package.json. Does not alter any behavior.

0.7.3 (2020-12-01)

  • Re-publishing package. Previous version had an old build artifact.

0.7.2 (2020-12-01)

  • Fixed bug that completely broke the token flow.

0.7.1 (2020-11-30)

  • Fix bug in auto-refresh

0.7.0 (2020-11-30)

  • Ensure that only 1 refresh operation will happen in parallel. If there are multiple things triggering the refresh, all will wait for the first one to finish.
  • Automatically schedule a refresh operation 1 minute before the access token expires, if the expiry time is known.
  • BC Break: If a token is known when setting up OAuth2, this now needs to be passed as the second argument. The old behavior still works but will emit a warning, and will be removed in a future release.
  • 'OAuth2Token' type is now exported.

0.6.1 (2020-11-19)

  • 34: Refresh operation failed for the authorization_code flow.

0.6.0 (2020-11-09)

  • Added a onAuthError event, allowing users to intercept this event and re-authenticate.
  • Simplify types a bit. More duplication in the library, but this should result in easier to read errors.
  • Typescript 4
  • Switch from tslint to eslint.
  • Webpack 5

0.5.0 (2020-04-19)

  • Added a fetchMw() function that takes a next argument so this package can behave as a more regular middleware.

0.4.2 (2019-12-09)

  • Files were not correctly built in the last release.

0.4.1 (2019-12-09)

  • Error code 401 will be submitted when authentication fails. Before, we would just forward the error code from the OAuth2 server, but this doesn't make a lot of sense for a fetch() user, as the error might be misinterpreted as an error unrelated to auth.

0.4.0 (2019-11-06)

  • Added a getOptions() method, which allows a user to get all current tokens and store them in LocalStorage. These options can be used as-is in the constructor.

0.3.5 (2019-09-05)

  • Include typescript sourcefiles in NPM package, for IDE's.

0.3.4 (2019-03-19)

  • This package now throws OAuth2Error classes for server-side errors.

0.3.3 (2019-03-18)

  • When refreshing a token, browsers don't allow re-use of the same Request object. Now we're cloning it before use.

0.3.2 (2019-03-13)

  • When refreshing a token, and there's no client_secret, the client_id must be sent in the body.

0.3.1 (2019-03-13)

  • Now correctly exporting all the right symbols.

0.3.0 (2019-03-13)

  • Library is refactored and now uses a class.
  • Support for authorization_code grant type.
  • Exposing some more information to uses.
  • Add a new onTokenUpdate hook for custom storage.
  • It's now possible to construct a client with an existing (old) Access and/or refresh token.

0.2.1 (2019-03-13)

  • Shipping dist/ instead of src/.
  • Making a browser build lean by not relying on querystring or Buffer.

0.2.0 (2019-03-12)

  • First public version
  • Support for client_credentials, password and refresh_token.
  • Will automatically attempt to refresh tokens if it knows an access token is expired.

[1]: https://datatracker.ietf.org/doc/html/rfc7636 "Proof Key for Code Exchange by OAuth Public Clients" [2]: https://datatracker.ietf.org/doc/html/rfc8414 "OAuth 2.0 Authorization Server Metadata" [3]: https://datatracker.ietf.org/doc/html/rfc7662 "OAuth 2.0 Token Introspection"