MoneyHub.Auth (MoneyHub v1.0.0)

Copy Markdown View Source

OIDC authentication flows: Pushed Authorisation Requests, authorisation URLs, code exchange, and client-credentials tokens.

Moneyhub's auth flow has three shapes depending on what you're doing:

  1. Bank connection (AIS) - drive the user through /oidc/auth (or a PAR-backed request_uri) to connect a bank account, then exchange the returned code for tokens.
  2. Payments (PIS) - same shape, but the claims parameter carries a mh:payment / mh:recurring_payment / mh:standing_order claim instead of (or alongside) AIS scopes.
  3. Ongoing data access - once a user is registered (their sub is known from step 1's id_token), fetch a client_credentials token scoped to that user via token_for_user/2 and call the data API directly - no further browser redirect needed.

Example: connect a bank account (ongoing access, new user)

claims = MoneyHub.Claims.new() |> MoneyHub.Claims.put_sub()

{:ok, %{url: url, request_uri: request_uri}} =
  MoneyHub.Auth.pushed_authorisation_request(config,
    scope: MoneyHub.Scopes.ais_offline(),
    claims: claims
  )

# redirect the user's browser to `url`, they authenticate at their
# bank, and your redirect_uri receives `?code=...`

{:ok, tokens} = MoneyHub.Auth.exchange_code(config, code)
{:ok, claims} = MoneyHub.Auth.IdToken.verify(tokens.id_token, config)
user_id = claims["sub"]

Example: get a data token for an already-connected user

{:ok, token} = MoneyHub.Auth.token_for_user(config, user_id)
{:ok, accounts} = MoneyHub.Accounts.list(config, token.access_token)

Summary

Functions

Builds an /oidc/auth authorisation URL.

Exchanges an authorisation code (returned to your redirect_uri) for tokens at /oidc/token, using authorization_code grant.

Sends a Pushed Authorisation Request (PAR) to /oidc/request and returns the request_uri to embed in the authorisation URL, alongside the ready-to-use full URL.

Exchanges a refresh_token for a fresh token set, using refresh_token grant. Requires the original authorisation to have included offline_access.

Fetches a client_credentials token, optionally scoped to a specific Moneyhub user (via the sub claim) for ongoing data access.

Types

tokens()

@type tokens() :: %{
  access_token: String.t(),
  id_token: String.t() | nil,
  refresh_token: String.t() | nil,
  token_type: String.t(),
  expires_in: integer() | nil,
  scope: String.t() | nil
}

Functions

authorisation_url(config, opts)

@spec authorisation_url(
  MoneyHub.Config.t(),
  keyword()
) :: String.t()

Builds an /oidc/auth authorisation URL.

Pass either the same options as pushed_authorisation_request/2 (:scope, :claims, :state, ...) to build a URL with parameters inline, or request_uri: (as returned by pushed_authorisation_request/2) to build a PAR-backed URL.

exchange_code(config, code, opts \\ [])

@spec exchange_code(MoneyHub.Config.t(), String.t(), keyword()) ::
  {:ok, tokens()} | {:error, MoneyHub.Error.t()}

Exchanges an authorisation code (returned to your redirect_uri) for tokens at /oidc/token, using authorization_code grant.

pushed_authorisation_request(config, opts)

@spec pushed_authorisation_request(
  MoneyHub.Config.t(),
  keyword()
) ::
  {:ok, %{request_uri: String.t(), expires_in: integer(), url: String.t()}}
  | {:error, MoneyHub.Error.t()}

Sends a Pushed Authorisation Request (PAR) to /oidc/request and returns the request_uri to embed in the authorisation URL, alongside the ready-to-use full URL.

PAR keeps sensitive parameters (notably claims, which can be large and contain payment payloads) off of the browser's URL bar entirely: only an opaque request_uri reference is exposed client-side.

Options

  • :scope - required. A space-delimited scope string, e.g. from MoneyHub.Scopes.
  • :claims - a MoneyHub.Claims map, or any term accepted by Jason.encode!/1, to be embedded as the claims parameter.
  • :state - an opaque value round-tripped back on redirect. Generated randomly if omitted.
  • :redirect_uri - overrides config.redirect_uri for this request.
  • :response_type - defaults to "code".

refresh_token(config, refresh_token)

@spec refresh_token(MoneyHub.Config.t(), String.t()) ::
  {:ok, tokens()} | {:error, MoneyHub.Error.t()}

Exchanges a refresh_token for a fresh token set, using refresh_token grant. Requires the original authorisation to have included offline_access.

token_for_user(config, user_id \\ nil, opts \\ [])

@spec token_for_user(MoneyHub.Config.t(), String.t() | nil, keyword()) ::
  {:ok, tokens()} | {:error, MoneyHub.Error.t()}

Fetches a client_credentials token, optionally scoped to a specific Moneyhub user (via the sub claim) for ongoing data access.

Once a user has completed an AIS connection (their sub known from the id_token of exchange_code/3), call this to obtain a fresh access_token for reading that user's data - no browser interaction required.