Atex.OAuth.Plug (atex v0.9.1)

View Source

Plug router for handling AT Protocol's OAuth flow.

This module provides four 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
  • GET /logout - Logs out the current session and revokes tokens

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.

An optional :logout_callback option can be provided for handling logout redirects. If not provided, the user is redirected to "/".

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 callbacks.

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, []},
      logout_callback: {__MODULE__, :logout_callback, []}
    ]

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

  def logout_callback(conn) do
    # Handle logout redirect
    conn
    |> put_resp_header("Location", "/login")
    |> 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 the following in conn.session:

The full session credentials (tokens, DPoP key, etc.) are stored in Atex.OAuth.SessionStore and looked up by the composite key.

Summary

Functions

Callback implementation for Plug.call/2.

Callback implementation for Plug.init/1.

Revokes a session, removing it from the store and cleaning up the Plug session.

Functions

call(conn, opts)

Callback implementation for Plug.call/2.

init(opts)

Callback implementation for Plug.init/1.

revoke_session(conn, session_key)

@spec revoke_session(Plug.Conn.t(), String.t()) ::
  {:ok, Plug.Conn.t()} | {:error, :not_found}

Revokes a session, removing it from the store and cleaning up the Plug session.

This function:

  1. Deletes the session from Atex.OAuth.SessionStore
  2. Revokes tokens with the authorization server
  3. Removes the session key from the Plug session's active session
  4. If the deleted session was the active one, switches to another or clears it

Parameters

  • conn - The Plug connection
  • session_key - The composite session key to revoke

Returns

  • {:ok, conn} - Session revoked; the returned conn has updated session data
  • {:error, :not_found} - Session key not found