OneAuth

CIHex.pmDocumentation

A simple, database-free alternative to HTTP Basic Auth with session-based authentication for Plug-compatible applications.

See the Releases page for a changelog of notable changes between versions.

Warning

OneAuth is under active development. APIs may change before the first stable release.

Table of Contents

Why OneAuth?

Some applications only need to protect access behind a single account — a personal dashboard, an internal tool, an admin interface. Pulling in a full user-authentication system for that is overkill, and relying on the browser's built-in HTTP Basic Auth dialog is a poor experience with no real logout flow.

OneAuth fills that gap: a normal, session-backed login flow, configured with a single username and password, and no database required.

Good fits include:

OneAuth is not intended for applications requiring:

OneAuth is built on Plug, so it isn't tied to a specific web framework. It works with any Plug-compatible application, including Phoenix and other frameworks built on top of Plug. The examples below use Phoenix since it's the most common target.

Features

Installation

Add :one_auth to your list of dependencies in mix.exs:

def deps do
[
{:one_auth, "~> 0.1"}
]
end

Configuration

OneAuth is configured through your application's runtime.exs.

The minimum required configuration is:

config :one_auth,
username: System.fetch_env!("ONE_AUTH_USERNAME"),
password: System.fetch_env!("ONE_AUTH_PASSWORD"),
signing_secret: System.fetch_env!("ONE_AUTH_SIGNING_SECRET")

Additional options are available for customizing session behavior and routes. See the Configuration guide for the full list.

Phoenix Setup

Add OneAuth.Plug.LoadSession to your :browser pipeline, after :fetch_session, so the current session is loaded on every request. Add a separate :require_auth pipeline for routes that need to be protected:

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
# ...
plug OneAuth.Plug.LoadSession
end
pipeline :require_auth do
plug OneAuth.Plug.RequireAuth
end

Login Example

Add routes for rendering the login form and submitting credentials, scoped to your :browser pipeline:

scope "/", MyAppWeb do
pipe_through :browser
get "/login", SessionController, :new
post "/login", SessionController, :create
end

Handle both actions in a controller. OneAuth.login/3 verifies the submitted credentials and starts the session:

defmodule MyAppWeb.SessionController do
use MyAppWeb, :controller
def new(conn, _params) do
render(conn, :new)
end
def create(conn, %{"username" => username, "password" => password}) do
case OneAuth.login(conn, username, password) do
{:ok, conn} ->
redirect(conn, to: OneAuth.login_redirect_path(conn))
:error ->
conn
|> put_flash(:error, "Invalid username or password")
|> redirect(to: OneAuth.login_path(conn))
end
end
end

OneAuth.login_redirect_path/1 is where a successful login sends the user. If they were redirected to /login from a protected page (see Protecting Routes), they're sent back there. Otherwise it falls back to :login_redirect_path, which defaults to "/".

Then render the form itself. OneAuth.login_path/1 gives you the correct action URL without hardcoding it:

defmodule MyAppWeb.SessionHTML do
use MyAppWeb, :html
def new(assigns) do
~H"""
<h1>Log in to your account</h1>
<.simple_form for={%{}} action={OneAuth.login_path(@conn)}>
<input
id="username"
type="text"
name="username"
placeholder="Username"
autocomplete="username"
aria-label="Username"
required
/>
<input
id="password"
type="password"
name="password"
placeholder="Password"
autocomplete="current-password"
aria-label="Password"
required
/>
<:actions>
<.button phx-disable-with="Logging in...">Log in</.button>
</:actions>
</.simple_form>
"""
end
end

Protecting Routes

Pipe any scope you want to protect through :require_auth in addition to :browser. Unauthenticated requests are redirected to the :login_path ("/login" by default), with the originally requested path remembered so the user is sent back there after logging in:

scope "/admin", MyAppWeb do
pipe_through [:browser, :require_auth]
get "/", AdminController, :index
end

Everything under /admin in this example now requires a logged-in session — no per-action checks needed.

Accessing the Current User

Once a session is loaded, OneAuth.current_user/1 returns the logged-in username (or nil if there isn't one). This works anywhere you have access to conn, including in controllers and templates:

def index(conn, _params) do
current_user = OneAuth.current_user(conn)
render(conn, :index, current_user: current_user)
end

Then reference the assign in your template:

<p>Signed in as {@current_user}</p>

In a Layout

A single controller assign only covers one action. For something like a root layout, where you want a greeting or login/logout link on every page, assign current_user once in your pipeline instead:

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
# ...
plug OneAuth.Plug.LoadSession
plug :assign_current_user
end
def assign_current_user(conn, _opts) do
assign(conn, :current_user, OneAuth.current_user(conn))
end

@current_user is now available in every template rendered through that pipeline, including your root layout:

<header>
<%= if @current_user do %>
<span>Hi, {@current_user}</span>
<.link href={~p"/logout"} method="delete">Log out</.link>
<% else %>
<.link href={~p"/login"}>Log in</.link>
<% end %>
</header>

Logout Example

Add a logout route:

scope "/", MyAppWeb do
pipe_through :browser
delete "/logout", SessionController, :delete
end

Handle it in your controller:

def delete(conn, _params) do
conn
|> OneAuth.logout()
|> redirect(to: OneAuth.login_path(conn))
end

Wire up a logout link wherever you need one:

<.link href={~p"/logout"} method="delete">Log out</.link>

Public API

OneAuth intentionally exposes a small public API.

OneAuth.login(conn, username, password)
OneAuth.login_path(conn)
OneAuth.login_redirect_path(conn)
OneAuth.current_user(conn)
OneAuth.logout(conn)

Most applications should only need these functions together with OneAuth.Plug.LoadSession and OneAuth.Plug.RequireAuth.

License

OneAuth is released under the MIT License.