# ueberauth_mastodon
Log into [Mastodon](https://joinmastodon.org/) and [Pleroma](https://pleroma.social/) with [Überauth](https://github.com/ueberauth/ueberauth).
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
```elixir
# 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](https://github.com/teamon/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`
```elixir
# 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`:
```elixir
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.
```elixir
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.
```elixir
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:
```elixir
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:
```elixir
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`:
```elixir
def deps do
[
{:ueberauth, "~> 0.7.0"},
{:ueberauth_mastodon, "~> 0.2.1"},
# 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.