ExSaml

Hex.pmDocs

SAML 2.0 Service Provider (SP) library for Elixir/Phoenix applications.

Originally built from the Samly codebase (by handnot2), before the Dropbox fork was created. Dropbox's fork has since been declared unmaintained. ExSaml is the actively maintained successor, with enhanced security, configurable caching, and streamlined routing.

Features

Installation

Add ex_saml to your dependencies in mix.exs:

def deps do
  [
    {:ex_saml, "~> 1.0"}
  ]
end

Configuration

Service Provider

config :ex_saml, ExSaml.Provider,
  service_providers: [
    %{
      id: "my_sp",
      entity_id: "urn:myapp:sp",
      certfile: "path/to/sp.crt",
      keyfile: "path/to/sp.key",
      # Optional
      contact_name: "Admin",
      contact_email: "admin@example.com",
      org_name: "My Org",
      org_displayname: "My Organization",
      org_url: "https://example.com"
    }
  ]

You can also provide cert and key directly instead of file paths.

Identity Provider

config :ex_saml, ExSaml.Provider,
  identity_providers: [
    %{
      id: "my_idp",
      sp_id: "my_sp",
      base_url: "https://myapp.example.com",
      metadata_file: "path/to/idp_metadata.xml",
      # Or inline: metadata: "<EntityDescriptor ...>",
      nameid_format: :email,
      sign_requests: true,
      sign_metadata: true,
      signed_assertion_in_resp: true,
      signed_envelopes_in_resp: false,
      allow_idp_initiated_flow: true,
      use_redirect_for_req: false,
      use_redirect_for_slo: false,
      allowed_target_urls: ["https://myapp.example.com/dashboard"]
    }
  ],
  idp_id_from: :path_segment  # or :subdomain

Supported nameid_format values: :email, :x509, :windows, :krb, :persistent, :transient.

State Store

Choose where authenticated assertions are stored:

# ETS (default)
config :ex_saml, ExSaml.State,
  store: ExSaml.State.ETS

# Plug Session
config :ex_saml, ExSaml.State,
  store: ExSaml.State.Session

# Nebulex Cache
config :ex_saml, ExSaml.State,
  store: ExSaml.State.Cache

Cache

Configure the Nebulex cache module used for assertions and relay state:

config :ex_saml, cache: MyApp.Cache

Dynamic Provider Loading

For loading providers from a database at runtime:

config :ex_saml,
  service_providers_accessor: &MyApp.Saml.service_providers/0,
  identity_providers_accessor: &MyApp.Saml.identity_providers/0

Setup

Supervision Tree

Add the provider to your application's supervision tree:

children = [
  ExSaml.Provider
]

Router

Forward SAML routes in your Phoenix router:

forward "/sso", ExSaml.Router

This exposes:

SP endpoints (metadata, ACS, SLO) are configured via ExSaml.Helper URI builders and handled by ExSaml.SPHandler.

Usage

Requesting an IdP Directly

ExSaml.AuthHandler.request_idp(conn, idp_id)

Initiating Sign-In

ExSaml.AuthHandler.send_signin_req(conn)

Initiating Sign-Out

ExSaml.AuthHandler.send_signout_req(conn)

Retrieving the Active Assertion

assertion = ExSaml.get_active_assertion(conn)

To get a specific attribute:

email = ExSaml.get_attribute(assertion, "email")

The ExSaml.Assertion struct contains:

Architecture

Request
  |
  v
ExSaml.Router
  |-- /auth/*      -> ExSaml.AuthRouter -> ExSaml.AuthHandler
  |-- /csp-report  -> ExSaml.CsprRouter
  |
  v
ExSaml.SecurityPlug (CSP nonce, security headers)
  |
  v
ExSaml.Provider (GenServer managing SP/IdP state)
  |
  v
ExSaml.SPHandler (metadata, ACS, SLO)
  |
  v
ExSaml.State (assertion storage: ETS | Session | Cache)

Migrating from Samly

If you're coming from Samly or the Dropbox fork, see the Migration Guide for a step-by-step walkthrough covering module renaming, config changes, removed features, and a migration checklist.

Key Differences

Documentation

Full documentation is available on HexDocs.

License

See LICENSE for details.