BexioApiClient (bexio_api_client v2.0.0)

Copy Markdown

base functionality like refreshing the access token.

The API Documentation for Bexio can be found under https://docs.bexio.com.

Fields or arguments with date time reference expect them in the time zone Europe/Zurich.

Requirements:

  • Http Client: ** Req: the api client depends on Req.

Refresh tokens

Bexio does not currently rotate refresh tokens — the same refresh token keeps working — and the access token refresher does not manage rotation. Token responses expose the refresh_token through access_token/3 anyway, so if Bexio ever enables rotation (a Keycloak realm option), host applications can persist the new token from the map and pass it to new/3 themselves. Hammering the token endpoint with failing refreshes can get the OAuth client blacklisted by Bexio (per IP), which is why failed refreshes are backed off instead of retried.

Summary

Functions

Fetches a token response from the Bexio (Keycloak) token endpoint.

Creates a new client using the never ending all powerful API Token.

Creates a new client using the client id, client secret and refresh token. Be aware that getting this information is part of the OpenID flow which is not in the scope of this library.

Removes all expired access tokens from the ETS table.

Functions

access_token(refresh_token, client_id, client_secret)

@spec access_token(String.t(), String.t(), String.t()) ::
  {:ok,
   %{
     :access_token => String.t(),
     :expires_in => integer(),
     optional(:refresh_token) => String.t() | nil,
     optional(:id_token) => String.t() | nil,
     optional(:scope) => String.t() | nil,
     optional(:token_type) => String.t() | nil
   }}
  | {:error, {:unexpected_response, integer(), any()}}
  | {:error, {:request_failed, any()}}

Fetches a token response from the Bexio (Keycloak) token endpoint.

Arguments:

  • refresh_token -> the refresh token we need the access token for
  • client_id -> the client id of our application that also got the refresh token
  • client_secret -> the client secret of our application

Response

On success, the full token map is returned, including the refresh_token the response contains (Bexio currently keeps refresh tokens stable — the same token comes back).

{:ok, %{access_token: "", expires_in: 3600, id_token: nil, refresh_token: nil, scope: nil, token_type: nil}}

Every non-success response (including 2xx with an unexpected body) returns the status and body and logs the full Req.Response, so failures can be classified afterwards:

{:error, {:unexpected_response, 403, %{"reason" => "blacklisted", "status" => "blocked"}}}

Transport failures return:

{:error, {:request_failed, exception}}

This function never retries on its own; retry policy belongs to BexioApiClient.Req.AccessTokenRefresher.

new(api_token)

@spec new(String.t()) :: Req.Request.t()

Creates a new client using the never ending all powerful API Token.

new(client_id, client_secret, refresh_token, opts \\ [])

@spec new(String.t(), String.t(), String.t(), keyword()) :: Req.Request.t()

Creates a new client using the client id, client secret and refresh token. Be aware that getting this information is part of the OpenID flow which is not in the scope of this library.

Additional opts are passed to BexioApiClient.Req.AccessTokenRefresher.attach/2, e.g. on_refresh_gave_up: {module, function, args}.

remove_expired_access_tokens()

Removes all expired access tokens from the ETS table.