Atex.OAuth.Plug (atex v0.7.1)

View Source

Plug router for handling AT Protocol's OAuth flow.

This module provides three endpoints:

  • GET /login?handle=<handle> - Initiates the OAuth authorization flow for a given handle
  • GET /callback - Handles the OAuth callback after user authorization
  • GET /client-metadata.json - Serves the OAuth client metadata

Usage

This module requires Plug.Session to be in your pipeline, as well as secret_key_base to have been set on your connections. Ideally it should be routed to via Plug.Router.forward/2, under a route like "/oauth".

The plug requires a :callback option that must be an MFA tuple (Module, Function, Args). This callback is invoked after successful OAuth authentication, receiving the connection with the authenticated session data.

Error Handling

Atex.OAuth.Error exceptions are raised when errors occur during the OAuth flow (e.g. an invalid handle is provided, or validation failed). You should implement a Plug.ErrorHandler to catch and handle these exceptions gracefully.

Example

Example implementation showing how to set up the OAuth plug with proper session handling, error handling, and a callback function.

defmodule ExampleOAuthPlug do
  use Plug.Router
  use Plug.ErrorHandler

  plug :put_secret_key_base

  plug Plug.Session,
    store: :cookie,
    key: "atex-oauth",
    signing_salt: "signing-salt"

  plug :match
  plug :dispatch

  forward "/oauth", to: Atex.OAuth.Plug, init_opts: [callback: {__MODULE__, :oauth_callback, []}]

  def oauth_callback(conn) do
    # Handle successful OAuth authentication
    conn
    |> put_resp_header("Location", "/dashboard")
    |> resp(307, "")
    |> send_resp()
  end

  def put_secret_key_base(conn, _) do
    put_in(
      conn.secret_key_base,
      "very long key base with at least 64 bytes"
    )
  end

  # Error handler for OAuth exceptions
  @impl Plug.ErrorHandler
  def handle_errors(conn, %{kind: :error, reason: %Atex.OAuth.Error{} = error, stack: _stack}) do
    status = case error.reason do
      reason when reason in [:missing_handle, :invalid_handle, :invalid_callback_request, :issuer_mismatch] -> 400
      _ -> 500
    end

    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(status, error.message)
  end

  # Fallback for other errors
  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    send_resp(conn, conn.status, "Something went wrong")
  end
end

Session Storage

After successful authentication, the plug stores these in the session:

  • :tokens - The access token response containing access_token, refresh_token, did, and expires_at
  • :dpop_nonce -
  • :dpop_key - The DPoP JWK for generating DPoP proofs

Summary

Functions

Callback implementation for Plug.call/2.

Callback implementation for Plug.init/1.

Functions

call(conn, opts)

Callback implementation for Plug.call/2.

init(opts)

Callback implementation for Plug.init/1.