ueberauth_mastodon View Source

Log into Mastodon and Pleroma with Überauth.

This library makes it easy to spin up Elixir microservices to run alongside your social media website.

You can configure one or more Mastodon/Pleroma servers as login options.

Usage guide

Configuration

# Tesla
config :tesla, adapter: Tesla.Adapter.Hackney

# Ueberauth
config :ueberauth, Ueberauth,
  providers: [
    # You will create routes matching the provider name:
    # - /auth/mastodon
    # - /auth/mastodon/callback
    mastodon: {Ueberauth.Strategy.Mastodon, [
      # instance: "https://example.tld",
      # client_id: "********",
      # client_secret: "********",
      # scope: "read write follow"
    ]},

    # This one will be at /auth/gleasonator
    gleasonator:
      {Ueberauth.Strategy.Mastodon,
       [
         # You MUST provide an instance.
         instance: "https://gleasonator.com",
         # You MUST provide app credentials.
         # Generate your app before getting started.
         client_id: "3WCR-5e3nOg2SJ90W134VLIIwmib2T96qsXWSJAAEUs",
         client_secret: "r-vCWcOk_7IY202yYMMgEHEVEtd5Gv4tlByZqVChRm0",
         scope: "read write follow"
       ]}
  ]

Tesla

Under the hood, ueberauth_mastodon uses Tesla to make HTTP requests.

Tesla is not an HTTP client itself, but a flexible layer for switchable HTTP clients. In this guide we use Hackney, but you can use whatever you want. Just don't leave it blank.

Options
  • instance (required) - A URL to the Mastodon/Pleroma instance.
  • client_id (required) - Generated by an app. Create the app first.
  • client_secret (required) - Generated by an app. Create the app first.
  • scope - Space-separated list of scopes, eg read write follow. It defaults to read.
Advanced
  • redirect_uri - Override the redirect URL. By default it goes to /auth/:provider/callback
  • uid_field - Which field from Mastodon API to map to Überauth. It's set to "url" (the ActivityPub ID) by default.
Runtime configuration

All configuration options are strings. For runtime configuration, it's possible to pass values that will be evaulated to strings:

  • string, eg "123456"
  • {m, f, a} tuple, eg {System, :get_env, ["CLIENT_SECRET"]}
  • function, eg fn -> System.get_env("CLIENT_SECRET") end
# Runtime configuration
config :ueberauth, Ueberauth,
  providers: [
    mastodon: {Ueberauth.Strategy.Mastodon, [

      # Just a plain old hardcoded string
      instance: "https://example.tld",

      # {module, function, args} format
      client_id: {System, :get_env, "MASTODON_CLIENT_ID"},

      # Anonymous function
      client_secret: fn -> System.get_env("MASTODON_CLIENT_SECRET") end
    ]}
  ]

Routes

You'll need to create matching routes in router.ex:

scope "/auth", PatronWeb do
  pipe_through [:browser, Ueberauth]

  get "/mastodon", AuthController, :request
  get "/mastodon/callback", AuthController, :callback

  # You don't have to have more than one, but you can have any number
  get "/gleasonator", AuthController, :request
  get "/gleasonator/callback", AuthController, :callback
end

The Ueberauth plug will match names from your config and intercept the conn before it arrives at your controller.

request/2

You do not need to implement the request/2 view in your controller. The plug intercepts it and does a redirect before it hits your controller.

Just put a link to /auth/:provider somewhere on your website, and it will redirect to the OAuth signup page.

callback/2

You must provide a callback/2 view.

def callback(%{assigns: %{ueberauth_auth: auth} = conn, _params) do
  # TODO: Store the auth somewhere
  conn
end

Controller

You'll need to create a controller to handle the callback. Below is an example of a full controller.

defmodule PatronWeb.AuthController do
  use PatronWeb, :controller

  alias Ueberauth.Auth
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Failure
  alias Ueberauth.Failure.Error

  # /auth/:provider/callback
  # After the user authorizes the OAuth form, they'll be redirected back here.
  def callback(
        # An `:ueberauth_auth` key is provided upon success.
        # It contains a `%Ueberauth.Auth{}` struct.
        # https://hexdocs.pm/ueberauth/Ueberauth.Auth.html#t:t/0
        %{assigns: %{ueberauth_auth: %Auth{uid: uid, credentials: %Credentials{} = credentials}}} = conn,
        _params
      ) do
    conn
    # Store the credentials in a cookie, or anywhere else
    |> put_session(:token_data, credentials)
    |> put_session(:uid, uid)
    |> redirect(to: "/")
  end

  def callback(
        # Upon failure, you'll get `:ueberauth_failure`.
        # It contains a `%Ueberauth.Failure{}` struct.
        # https://hexdocs.pm/ueberauth/Ueberauth.Failure.html#t:t/0
        %{assigns: %{ueberauth_failure: %Failure{errors: [%Error{message: message} | _]}}} = conn,
        _params
      ) do
    conn
    |> put_flash(:error, message)
    |> redirect(to: "/")
  end

  # If neither exist, just redirect home
  def callback(conn, _params) do
    redirect(conn, to: "/")
  end
end

Authentication Plug

Finally, you'll likely want to create a plug to authenticate the user on pageload. This is one possible way:

defmodule PatronWeb.Plugs.BootstrapUser do
  import Plug.Conn
  alias Patron.User
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Strategy.Mastodon

  @behaviour Plug

  def init(_), do: nil

  def call(conn, _) do
    # Get the token set from the callback
    case get_session(conn, :token_data) do
      nil -> conn
      # Make an HTTP request
      %Credentials{token: token} -> verify_token(conn, token)
      # Delete invalid token
      _ -> delete_session(conn, :token_data)
    end
  end

  # Fetch the account from the token
  defp verify_token(conn, token) do
    # The uid is an ActivityPub ID, which serves as a convenient base URL
    with %Credentials{uid: ap_id} <- get_session(conn, :token_data),
         {:ok, %{status: 200, body: %{"url" => ap_id} = data}} <-
           Mastodon.API.account_verify_credentials(ap_id, token) do
      conn
      |> assign(:user_data, data)
    else
      _ -> delete_session(conn, :token_data)
    end
  end
end

And in the router:

pipeline :browser do
  # ...
  plug PatronWeb.Plugs.BootstrapUser
end

Installation

Add ueberauth, ueberauth_mastodon, and a Tesla adapter to your list of dependencies in mix.exs:

def deps do
  [
    {:ueberauth, "~> 0.7.0"},
    {:ueberauth_mastodon, "~> 0.1.0"},

    # For `Tesla.Adapter.Hackney` to work
    {:hackney, "~> 1.18"}
  ]
end

License

ueberauth_mastodon is licensed under the MIT license. See LICENSE.md for the full text.