openid-client

repository·main·Indexed 25 days ago

https://github.com/panva/openid-client

A comprehensive OAuth 2 and OpenID Connect Client API for JavaScript runtimes (Node.js v20+, Bun, Deno, Cloudflare Workers, and Browsers). It supports various grants including Authorization Code, Client Credentials, Device Flow, and CIBA, as well as advanced security profiles like FAPI, DPoP, PKCE, and JAR/JARM. The library provides tools for client authentication, token management, and protected resource requests.

Tokens
38.3K
Snippets
70
Records
191
Agent score
81%

What's inside openid-client

  1. Overview of openid-client API Reference

    main

    The openid-client library provides a comprehensive set of tools for implementing OpenID Connect (OIDC) and OAuth 2.0 flows. The API is organized into several functional areas:

    • Grants: Implementation of various OAuth 2.0 flows including authorizationCodeGrant, clientCredentialsGrant, refreshTokenGrant, initiateDeviceAuthorization, and initiateBackchannelAuthentication (CIBA).
    • Authorization Request: Utilities for constructing requests, such as buildAuthorizationUrl, calculatePKCECodeChallenge, and generating randomNonce or randomState.
    • Configuration: Methods for global and instance-specific settings via discovery and the Configuration class.
    • Client Authentication: Support for multiple methods including ClientSecretBasic, ClientSecretJwt, ClientSecretPost, PrivateKeyJwt, and TlsClientAuth.
    • Protected Resource Requests: Methods to interact with APIs using tokens, such as fetchProtectedResource and fetchUserInfo.
    • Token Management: Tools for tokenIntrospection and tokenRevocation.
    • Security Extensions: Support for DPoP (Demonstrating Proof-of-Possession) and PKCE (Proof Key for Code Exchange).
  2. What is the Configuration class and how to obtain it

    main

    The Configuration class is an abstraction that combines OAuth 2.0 Authorization Server metadata and OAuth 2.0 Client metadata. It serves as the central object for interacting with an OpenID Provider.

    You can obtain a Configuration instance in two ways:

    1. Discovery (Recommended): Use the discovery function to automatically fetch the Authorization Server metadata using the Issuer Identifier.
    2. Constructor: Use the new Configuration() constructor if you already have the Server Metadata available upfront.
    // Recommended: Using discovery
    let server!: URL
    let clientId!: string
    let clientSecret!: string | undefined
    
    let config = await client.discovery(server, clientId, clientSecret)
    
    // Alternative: Using the constructor
    let serverMetadata!: client.ServerMetadata
    let clientId!: string
    let clientSecret!: string | undefined
    
    let config = new client.Configuration(serverMetadata, clientId, clientSecret)
  3. Avoid passing discovery URLs directly to discovery()

    main

    While discovery() accepts a URL pointing directly to an Authorization Server's discovery document (e.g., https://example.com/.well-known/openid-configuration), doing so is NOT RECOMMENDED.

    Passing a direct discovery document URL is a shorthand for fetching the JSON and passing it to the Configuration constructor, but it disables ServerMetadata.issuer validation. To maintain security, always pass the Issuer Identifier URL instead.

  4. Understand the ServerMetadata interface

    main

    The ServerMetadata interface represents the metadata returned by an Authorization Server (typically via the .well-known/openid-configuration endpoint). It describes the capabilities, endpoints, and supported algorithms of the server. This interface is used by openid-client to configure its internal state when interacting with a specific provider.

    Metadata can be accessed via an indexer using a string key, which returns a JsonValue or undefined.

  5. Quick start with openid-client discovery

    main

    To begin using openid-client, use the discovery method to fetch the Authorization Server's metadata. This returns a Configuration object used for all subsequent API calls. You need the server's issuer URL, your client ID, and your client secret.

    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,
    )
  6. Use Mutual-TLS (mTLS) with ClientMetadata

    main

    Setting use_mtls_endpoint_aliases to true in ClientMetadata indicates that the client must use mutual TLS endpoint aliases provided by the Authorization Server.

    To implement this, you must combine this setting with customFetch to provide a fetch implementation that supports client certificates (e.g., using undici in Node.js or Deno.createHttpClient in Deno). This is used to target security profiles utilizing Mutual-TLS for client authentication or sender-constrained tokens (RFC 8705).

    ### (Node.js) Using nodejs/undici for Mutual-TLS
    ```ts
    import * as undici from 'undici'
    
    let config!: client.Configuration
    let key!: string // PEM-encoded key
    let cert!: string // PEM-encoded certificate
    
    let agent = new undici.Agent({ connect: { key, cert } })
    
    config[client.customFetch] = (...args) =>
      // @ts-expect-error
      undici.fetch(args[0], { ...args[1], dispatcher: agent })

    (Deno) Using Deno.createHttpClient for Mutual-TLS

    let config!: client.Configuration
    let key!: string // PEM-encoded key
    let cert!: string // PEM-encoded certificate
    
    // @ts-expect-error
    let agent = Deno.createHttpClient({ key, cert })
    
    config[client.customFetch] = (...args) =>
      // @ts-expect-error
      fetch(args[0], { ...args[1], client: agent })
  7. Implement the Authorization Code Flow

    main

    The Authorization Code flow is used to obtain Access Tokens (and optionally Refresh Tokens) for end-users. The process involves two main steps:

    1. Redirecting the user: Generate an authorization URL using buildAuthorizationUrl. It is highly recommended to use PKCE (Proof Key for Code Exchange). You must generate a code_verifier and a code_challenge, and store the code_verifier and state in the user's session to recover them after the redirect.

    2. Exchanging the code: Once the user is redirected back to your redirect_uri, use authorizationCodeGrant to exchange the authorization code for tokens, providing the stored pkceCodeVerifier and expectedState.

    /**
     * Step 1: Build the authorization URL
     */
    let redirect_uri!: string
    let scope!: string
    let code_verifier: string = client.randomPKCECodeVerifier()
    let code_challenge: string = await client.calculatePKCECodeChallenge(code_verifier)
    let state!: string
    
    let parameters: Record<string, string> = {
      redirect_uri,
      scope,
      code_challenge,
      code_challenge_method: 'S256',
    }
    
    if (!config.serverMetadata().supportsPKCE()) {
      state = client.randomState()
      parameters.state = state
    }
    
    let redirectTo: URL = client.buildAuthorizationUrl(config, parameters)
    // Redirect the user to redirectTo.href
    
    /**
     * Step 2: Exchange code for tokens
     */
    let tokens: client.TokenEndpointResponse = await client.authorizationCodeGrant(
      config,
      getCurrentUrl(),
      {
        pkceCodeVerifier: code_verifier,
        expectedState: state,
      },
    )
  8. Implement Device Authorization Grant (Device Flow)

    main

    The Device Flow is suitable for devices with limited input capabilities.

    1. Call initiateDeviceAuthorization with the desired scope to get a user_code and verification_uri.
    2. Display these to the user.
    3. Use pollDeviceAuthorizationGrant to poll the token endpoint. This method will resolve only once the user has successfully authenticated.
    let scope!: string
    
    // 1. Initiate
    let response = await client.initiateDeviceAuthorization(config, { scope })
    
    console.log('User Code:', response.user_code)
    console.log('Verification URI:', response.verification_uri)
    
    // 2. Poll for tokens
    let tokens: client.TokenEndpointResponse =
      await client.pollDeviceAuthorizationGrant(config, response)
  9. Implement Client Credentials Grant

    main

    The Client Credentials flow is used to obtain Access Tokens for application-level access to third-party APIs, rather than on behalf of an end-user.

    let scope!: string
    let resource!: string // Resource Indicator of the Resource Server
    
    let tokens: client.TokenEndpointResponse = await client.clientCredentialsGrant(
      config,
      { scope, resource },
    )
  10. Implement OAuth 2.0 Grants

    main

    The library provides specialized functions for different grant types. Common grants include:

    • Authorization Code Grant: Use authorizationCodeGrant for standard web applications.
    • Client Credentials Grant: Use clientCredentialsGrant for machine-to-machine communication.
    • Refresh Token Grant: Use refreshTokenGrant to obtain new access tokens.
    • Device Authorization Grant: Use initiateDeviceAuthorization and pollDeviceAuthorizationGrant for devices with limited input capabilities.
    • Client-Initiated Backchannel Authentication (CIBA): Use initiateBackchannelAuthentication and pollBackchannelAuthenticationGrant.