OAuth 2.0 and OpenID Connect client library for JavaScript runtimes with comprehensive authentication flows and security features.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Complete implementation of OAuth 2.0 and OpenID Connect grant types including Client Credentials, Refresh Token, Device Authorization, Client-Initiated Backchannel Authentication (CIBA), and generic grant support.
Obtain access tokens using client credentials for machine-to-machine authentication.
/**
* Execute Client Credentials Grant
* @param config - Configuration instance
* @param parameters - Additional parameters (scope, resource, etc.)
* @param options - Grant options (DPoP, etc.)
* @returns Promise resolving to token response with helpers
*/
function clientCredentialsGrant(
config: Configuration,
parameters?: URLSearchParams | Record<string, string>,
options?: DPoPOptions
): Promise<TokenEndpointResponse & TokenEndpointResponseHelpers>;Usage Examples:
import * as client from "openid-client";
// Basic client credentials grant
const tokens = await client.clientCredentialsGrant(config);
// With scope and resource parameters
const tokens = await client.clientCredentialsGrant(config, {
scope: "read:users write:users",
resource: "https://api.example.com"
});
// With multiple resources (resource indicators)
const tokens = await client.clientCredentialsGrant(config, {
scope: "api:access",
resource: ["https://api1.example.com", "https://api2.example.com"]
});
console.log("Access Token:", tokens.access_token);
console.log("Token Type:", tokens.token_type);
console.log("Expires In:", tokens.expiresIn(), "seconds");Exchange refresh tokens for new access tokens.
/**
* Execute Refresh Token Grant
* @param config - Configuration instance
* @param refreshToken - Refresh token to exchange
* @param parameters - Additional parameters (scope, resource, etc.)
* @param options - Grant options (DPoP, etc.)
* @returns Promise resolving to token response with helpers
*/
function refreshTokenGrant(
config: Configuration,
refreshToken: string,
parameters?: URLSearchParams | Record<string, string>,
options?: DPoPOptions
): Promise<TokenEndpointResponse & TokenEndpointResponseHelpers>;Usage Examples:
import * as client from "openid-client";
// Basic refresh token grant
const newTokens = await client.refreshTokenGrant(
config,
existingTokens.refresh_token
);
// With scope parameter (potentially reducing scope)
const newTokens = await client.refreshTokenGrant(
config,
existingTokens.refresh_token,
{
scope: "read:profile" // reduced from original scope
}
);
// With resource indicators
const newTokens = await client.refreshTokenGrant(
config,
existingTokens.refresh_token,
{
resource: "https://api.example.com",
scope: "api:read api:write"
}
);
// Check if refresh token was rotated
if (newTokens.refresh_token && newTokens.refresh_token !== existingTokens.refresh_token) {
console.log("Refresh token was rotated");
// Store new refresh token
}Implement device flow for input-constrained devices.
/**
* Initiate Device Authorization Grant
* @param config - Configuration instance
* @param parameters - Authorization request parameters
* @returns Promise resolving to device authorization response
*/
function initiateDeviceAuthorization(
config: Configuration,
parameters: URLSearchParams | Record<string, string>
): Promise<DeviceAuthorizationResponse>;
/**
* Poll for Device Authorization Grant completion
* @param config - Configuration instance
* @param deviceAuthorizationResponse - Response from initiateDeviceAuthorization
* @param parameters - Additional token endpoint parameters
* @param options - Polling options
* @returns Promise resolving to token response when authorized
*/
function pollDeviceAuthorizationGrant(
config: Configuration,
deviceAuthorizationResponse: DeviceAuthorizationResponse,
parameters?: URLSearchParams | Record<string, string>,
options?: DeviceAuthorizationGrantPollOptions
): Promise<TokenEndpointResponse & TokenEndpointResponseHelpers>;Usage Examples:
import * as client from "openid-client";
// Initiate device flow
const deviceResponse = await client.initiateDeviceAuthorization(config, {
scope: "openid profile email"
});
// Display user instructions
console.log("Go to:", deviceResponse.verification_uri);
console.log("Enter code:", deviceResponse.user_code);
// Or use complete URI if available
if (deviceResponse.verification_uri_complete) {
console.log("Or visit:", deviceResponse.verification_uri_complete);
}
// Poll for completion
try {
const tokens = await client.pollDeviceAuthorizationGrant(
config,
deviceResponse
);
console.log("Device authorized!");
console.log("Access Token:", tokens.access_token);
console.log("ID Token Claims:", tokens.claims());
} catch (error) {
if (error.error === "access_denied") {
console.log("User denied the request");
} else if (error.error === "expired_token") {
console.log("Device code expired");
}
}
// Poll with timeout and additional parameters
const tokens = await client.pollDeviceAuthorizationGrant(
config,
deviceResponse,
{ resource: "https://api.example.com" }, // additional parameters
{
signal: AbortSignal.timeout(300000) // 5 minute timeout
}
);Implement CIBA flow for authentication without user interaction on the requesting device.
/**
* Initiate CIBA flow
* @param config - Configuration instance
* @param parameters - CIBA request parameters (must include one of login_hint, id_token_hint, or login_hint_token)
* @returns Promise resolving to backchannel authentication response
*/
function initiateBackchannelAuthentication(
config: Configuration,
parameters: URLSearchParams | Record<string, string>
): Promise<BackchannelAuthenticationResponse>;
/**
* Poll for CIBA completion
* @param config - Configuration instance
* @param backchannelAuthenticationResponse - Response from initiateBackchannelAuthentication
* @param parameters - Additional token endpoint parameters
* @param options - Polling options
* @returns Promise resolving to token response when authenticated
*/
function pollBackchannelAuthenticationGrant(
config: Configuration,
backchannelAuthenticationResponse: BackchannelAuthenticationResponse,
parameters?: URLSearchParams | Record<string, string>,
options?: BackchannelAuthenticationGrantPollOptions
): Promise<TokenEndpointResponse & TokenEndpointResponseHelpers>;Usage Examples:
import * as client from "openid-client";
// Initiate CIBA with login hint
const cibaResponse = await client.initiateBackchannelAuthentication(config, {
scope: "openid profile email",
login_hint: "user@example.com",
binding_message: "Please confirm transaction #12345"
});
console.log("Authentication initiated. Auth req ID:", cibaResponse.auth_req_id);
// Poll for completion (for poll mode)
try {
const tokens = await client.pollBackchannelAuthenticationGrant(
config,
cibaResponse
);
console.log("User authenticated!");
console.log("Access Token:", tokens.access_token);
console.log("ID Token Claims:", tokens.claims());
} catch (error) {
if (error.error === "access_denied") {
console.log("User denied authentication");
} else if (error.error === "expired_token") {
console.log("Authentication request expired");
}
}
// With ID token hint instead of login hint
const cibaResponse = await client.initiateBackchannelAuthentication(config, {
scope: "openid profile",
id_token_hint: previousIdToken,
user_code: "USER123" // optional user code for verification
});Execute any OAuth 2.0 grant type including custom grants.
/**
* Execute generic grant request
* @param config - Configuration instance
* @param grantType - Grant type identifier
* @param parameters - Grant-specific parameters
* @param options - Grant options (DPoP, etc.)
* @returns Promise resolving to token response with helpers
*/
function genericGrantRequest(
config: Configuration,
grantType: string,
parameters: URLSearchParams | Record<string, string>,
options?: DPoPOptions
): Promise<TokenEndpointResponse & TokenEndpointResponseHelpers>;Usage Examples:
import * as client from "openid-client";
// JWT Bearer Token Grant (RFC 7523)
const tokens = await client.genericGrantRequest(
config,
"urn:ietf:params:oauth:grant-type:jwt-bearer",
{
assertion: jwtAssertion,
scope: "api:access",
resource: "https://api.example.com"
}
);
// SAML 2.0 Bearer Assertion Grant (RFC 7522)
const tokens = await client.genericGrantRequest(
config,
"urn:ietf:params:oauth:grant-type:saml2-bearer",
{
assertion: samlAssertion,
scope: "read write"
}
);
// Token Exchange Grant (RFC 8693)
const tokens = await client.genericGrantRequest(
config,
"urn:ietf:params:oauth:grant-type:token-exchange",
{
subject_token: accessToken,
subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
resource: "https://api.example.com",
audience: "https://api.example.com"
}
);
// Custom grant type
const tokens = await client.genericGrantRequest(
config,
"urn:example:custom-grant",
{
custom_parameter: "value",
scope: "custom:access"
}
);interface DeviceAuthorizationResponse {
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete?: string;
expires_in: number;
interval?: number;
}
interface BackchannelAuthenticationResponse {
auth_req_id: string;
expires_in: number;
interval?: number;
}
interface DeviceAuthorizationGrantPollOptions extends DPoPOptions {
/** AbortSignal to stop polling */
signal?: AbortSignal;
}
interface BackchannelAuthenticationGrantPollOptions extends DPoPOptions {
/** AbortSignal to stop polling */
signal?: AbortSignal;
}interface TokenEndpointResponse {
access_token: string;
token_type: string;
expires_in?: number;
refresh_token?: string;
scope?: string;
id_token?: string;
// Additional server-specific properties may be present
}
interface TokenEndpointResponseHelpers {
/**
* Parse ID Token claims (if present)
* @returns ID Token claims or undefined
*/
claims(): IDToken | undefined;
/**
* Calculate seconds until token expiration
* @returns Seconds until expiration or undefined
*/
expiresIn(): number | undefined;
}All grant types support DPoP (Demonstrating Proof-of-Possession) for sender-constrained tokens:
interface DPoPOptions {
/** DPoP handle for proof-of-possession */
DPoP?: DPoPHandle;
}Usage with DPoP:
import * as client from "openid-client";
// Create DPoP key pair
const dpopKeyPair = await client.randomDPoPKeyPair();
const dpopHandle = client.getDPoPHandle(config, dpopKeyPair);
// Use with any grant
const tokens = await client.clientCredentialsGrant(
config,
{ scope: "api:access" },
{ DPoP: dpopHandle }
);
// Token will be sender-constrained if server supports DPoP
if (tokens.token_type === "dpop") {
console.log("Token is sender-constrained with DPoP");
}Install with Tessl CLI
npx tessl i tessl/npm-openid-client