Longbridge.OAuth (longbridge v0.1.0)

Copy Markdown View Source

OAuth 2.0 Authorization Code flow with PKCE for Longbridge OpenAPI.

Desktop / interactive (browser available)

{:ok, config} = Longbridge.OAuth.authorize("your-client-id")
{:ok, ctx} = Longbridge.QuoteContext.start_link(config)

The first call opens a browser, exchanges the code for a token, and persists the token to disk. Subsequent calls reuse the cached token transparently.

Server / headless (no browser)

  1. Run the authorization flow on a machine with a browser:

    Longbridge.OAuth.authorize("your-client-id")

    This writes the token to ~/.longbridge/openapi/tokens/<client_id>.

  2. Copy that file to the server (same path).

  3. On the server, load the persisted token:

    {:ok, config} = Longbridge.OAuth.load_token("your-client-id")

    Tokens auto-refresh via the refresh_token grant when expired. No browser, no interaction needed after the initial copy.

CLI compatibility

If you already ran longbridge auth login, the token is stored at the same path. load_token/1 picks it up transparently.

Token storage

Tokens are persisted as JSON under:

~/.longbridge/openapi/tokens/<client_id>

The file stores access_token, refresh_token, expires_at, token_type, and (when provided) the http_url used at auth time.

Registering a client

If you don't have a client_id yet:

{:ok, client_id} = Longbridge.OAuth.register_client("My App")

Summary

Functions

Runs the full OAuth 2.0 Authorization Code flow with PKCE.

Generates the authorization URL for the OAuth flow.

Exchanges an authorization code for an access token.

Exports the persisted token as a map.

Generates a PKCE code verifier (43-128 character URL-safe string).

Loads the persisted OAuth token and returns a Longbridge.Config.

Computes the PKCE S256 code challenge from a verifier.

Refreshes an expired access token using the persisted refresh token.

Registers a new OAuth client and returns the client_id.

Functions

authorize(client_id, opts \\ [])

@spec authorize(
  String.t(),
  keyword()
) :: {:ok, Longbridge.Config.t()} | {:error, term()}

Runs the full OAuth 2.0 Authorization Code flow with PKCE.

Starts a local callback server, opens the user's browser, waits for the callback, exchanges the code for a token, and persists the token to disk. Returns a Longbridge.Config ready for use.

Options

  • :callback_port — TCP port for the local callback server (default 60355). Must match a registered redirect URI.
  • :scope — OAuth scope (default "3").
  • :china — Use the .longbridge.cn endpoint (default false).
  • :http_url — Override the OAuth base URL.
  • :open_url_fn — Override the "open browser" function (default: &open_browser/1). Tests can pass a no-op.
  • :timeout — How long to wait for the user to approve (default 5 minutes).
  • :storage — Custom Longbridge.OAuth.TokenStorage implementation. Defaults to Longbridge.OAuth.FileTokenStorage (writes to ~/.longbridge/openapi/tokens/<client_id>).

authorize_url(client_id, redirect_uri, state, code_challenge, opts \\ [])

@spec authorize_url(String.t(), String.t(), String.t(), String.t(), keyword()) ::
  String.t()

Generates the authorization URL for the OAuth flow.

The user must open this URL in a browser and approve the request. After approval the browser redirects to redirect_uri with a code parameter.

code_challenge is the PKCE S256 challenge, produced by pkce_challenge(verifier).

exchange_code(client_id, code, redirect_uri, code_verifier, opts \\ [])

@spec exchange_code(String.t(), String.t(), String.t(), String.t(), keyword()) ::
  {:ok, map()} | {:error, term()}

Exchanges an authorization code for an access token.

Returns {:ok, token_map} with keys :access_token, :refresh_token, :expires_at, :token_type, and any other fields returned by the server (scope, user_id, sub_accounts).

export_token(client_id, opts \\ [])

@spec export_token(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Exports the persisted token as a map.

Useful when copying a token from a developer workstation to a production server, or when loading it into a secret manager.

Returns {:ok, token_map} or {:error, reason}.

generate_code_verifier()

@spec generate_code_verifier() :: String.t()

Generates a PKCE code verifier (43-128 character URL-safe string).

The output is 86 characters of base64url-encoded randomness, which is within the RFC 7636 range (43-128 chars).

load_token(client_id, opts \\ [])

@spec load_token(
  String.t(),
  keyword()
) ::
  {:ok, Longbridge.Config.t()}
  | {:error,
     :not_found
     | {:refresh_failed, term()}
     | {:refresh_token_revoked, String.t(), String.t() | nil}}

Loads the persisted OAuth token and returns a Longbridge.Config.

Designed for server-side programs that cannot open a browser. The token file must have been written by a previous authorize/2 call (e.g. on a developer's workstation).

If the access token is expired (or within :refresh_skew seconds of expiry), attempts a refresh via the stored refresh_token before returning.

Options

Error cases

  • {:error, :not_found} — No token persisted for this client_id.
  • {:error, {:refresh_failed, reason}} — Refresh attempted but failed for a non-OAuth reason (e.g. network error).
  • {:error, {:refresh_token_revoked, error, description}} — The refresh_token was rejected by the OAuth server (invalid_grant or similar). User must re-authorize.

pkce_challenge(verifier)

@spec pkce_challenge(String.t()) :: String.t()

Computes the PKCE S256 code challenge from a verifier.

Challenge = base64url(sha256(verifier)) without padding, per RFC 7636.

refresh_token(client_id, opts \\ [])

@spec refresh_token(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Refreshes an expired access token using the persisted refresh token.

If client_id is given, reads the refresh token from disk first. Returns {:ok, token_map} or {:error, reason}.

register_client(opts \\ [])

@spec register_client(keyword()) :: {:ok, String.t()} | {:error, term()}

Registers a new OAuth client and returns the client_id.

The client is registered with the default PKCE settings and a localhost redirect URI. Use this to obtain a client_id before calling authorize/2 for the first time.

Options

  • :client_name — Display name (default "My Longbridge OpenAPI").
  • :redirect_uris — List of redirect URIs (default ["http://127.0.0.1:60355/callback"]).
  • :http_url — Override the OAuth base URL.