AshAuthentication.Strategy.DynamicOidc (ash_authentication v5.0.0-rc.9)

Copy Markdown View Source

Strategy for authenticating against arbitrary OpenID Connect providers whose configuration lives in a database table rather than in your application's compile-time DSL.

This is the building block for B2B/multi-tenant SSO patterns: each row in your OidcConnection resource is one customer's IdP configuration (base_url, client_id, client_secret, plus optional UI metadata). At sign-in time the strategy queries the resource — typically scoped by the current Ash tenant — and runs the standard OIDC flow against the matched row.

Setup

First, define a connection resource using AshAuthentication.OidcConnection:

defmodule MyApp.Accounts.OidcConnection do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.OidcConnection],
    domain: MyApp.Accounts

  oidc_connection do
    domain MyApp.Accounts
  end

  postgres do
    table "oidc_connections"
    repo MyApp.Repo
  end
end

Then add the strategy to your user resource:

authentication do
  strategies do
    dynamic_oidc :sso do
      connection_resource MyApp.Accounts.OidcConnection
      identity_resource MyApp.Accounts.UserIdentity
      redirect_uri MyApp.Secrets
    end
  end
end

URL shape

The strategy generates two routes:

  • GET /<subject>/<strategy_name>/:connection_id/request — initiate sign-in for a specific connection (the user/UI is responsible for knowing which connection_id to send to).
  • GET /<subject>/<strategy_name>/callback — single, shared callback URL. Each customer's IdP admin only ever needs to register this URL as their redirect URI. The connection_id is remembered between request and callback via the user's session.

Tenant context

If your connection resource is multitenant, the strategy will scope the lookup using the tenant set on the conn (Ash.PlugHelpers.set_tenant/2). Set the tenant upstream of the auth router — typically in a Phoenix plug that maps subdomain or header to your tenant. If no tenant is set and the resource is multitenant, the lookup will fail.

Non-multitenant connection resources are also supported — the strategy simply queries globally.

More documentation

Summary

Types

t()

@type t() :: %AshAuthentication.Strategy.DynamicOidc{
  __connection_id__: term(),
  __spark_metadata__: Spark.Dsl.Entity.spark_meta(),
  assent_strategy: module(),
  auth_method: term(),
  authorization_params: keyword() | {module(), keyword()},
  authorize_url: term(),
  base_url: term(),
  client_authentication_method: nil | binary(),
  client_id: term(),
  client_secret: term(),
  code_verifier: term(),
  connection_resource: module(),
  icon: atom(),
  id_token_signed_response_alg: binary(),
  id_token_ttl_seconds: nil | pos_integer(),
  identity_relationship_name: atom(),
  identity_relationship_user_id_attribute: atom(),
  identity_resource: module() | false,
  name: atom(),
  nonce: boolean() | nil | binary() | {module(), keyword()},
  openid_configuration: nil | map(),
  openid_configuration_uri: nil | binary() | {module(), keyword()},
  prevent_hijacking?: boolean(),
  private_key: term(),
  private_key_id: term(),
  private_key_path: term(),
  provider: atom(),
  redirect_uri: nil | binary() | {module(), keyword()},
  register_action_name: atom(),
  registration_enabled?: boolean(),
  resource: module(),
  session_identifier: nil | :unsafe | :jti,
  sign_in_action_name: atom(),
  site: term(),
  strategy_module: module(),
  team_id: term(),
  token_url: term(),
  trusted_audiences: nil | [String.t()] | {module(), keyword()},
  user_url: term()
}

Functions

transform(entity, dsl_state)

Callback implementation for AshAuthentication.Strategy.Custom.transform/2.

verify(strategy, dsl_state)

Callback implementation for AshAuthentication.Strategy.Custom.verify/2.