lib/oidcc/plug/extract_authorization.ex

defmodule Oidcc.Plug.ExtractAuthorization do
  @moduledoc """
  Extract `authorization` request header

  This module should be used together with `Oidcc.Plug.IntrospectToken`,
  `Oidcc.Plug.LoadUserinfo` or `Oidcc.Plug.ValidateJwtToken`.

  ```elixir
  defmodule SampleAppWeb.Endpoint do
    use Phoenix.Endpoint, otp_app: :sample_app

    # ...

    plug Oidcc.Plug.ExtractAuthorization

    plug Oidcc.Plug.IntrospectToken, [...] # Check Token via Introspection
    plug Oidcc.Plug.LoadUserinfo, [...] # Check Token via Userinfo
    plug Oidcc.Plug.ValidateJwtToken, [...] # Check Token via JWT validation

    plug SampleAppWeb.Router
  end
  ```
  """
  @moduledoc since: "0.1.0"

  @behaviour Plug

  import Plug.Conn, only: [get_req_header: 2, put_private: 3, halt: 1, send_resp: 3]

  @typedoc """
  Plug Configuration Options

  ## Options

  * `send_invalid_header_response` - Customize Error Response for invalid header
  """
  @typedoc since: "0.1.0"
  @type opts :: [
          {:send_invalid_header_response,
           (conn :: Plug.Conn.t(), given_header :: [String.t()] -> Plug.Conn.t())}
        ]

  @impl Plug
  def init(opts),
    do:
      Keyword.validate!(opts,
        send_invalid_header_response: &__MODULE__.send_invalid_header_response/2
      )

  @impl Plug
  def call(%Plug.Conn{} = conn, opts) do
    send_invalid_header_response = Keyword.fetch!(opts, :send_invalid_header_response)

    case get_req_header(conn, "authorization") do
      ["Bearer " <> token | _rest] -> put_private(conn, __MODULE__, token)
      [] -> put_private(conn, __MODULE__, nil)
      header -> send_invalid_header_response.(conn, header)
    end
  end

  @doc false
  @spec send_invalid_header_response(conn :: Plug.Conn.t(), given_header :: [String.t()]) ::
          Plug.Conn.t()
  def send_invalid_header_response(conn, [header | _rest]) do
    conn
    |> halt()
    |> send_resp(:bad_request, """
    Invalid authorization Header

    Expected: Authorization: Bearer <token>
    Given: #{inspect(header)}
    """)
  end
end