# `Macaw.Auth.Plug`
[🔗](https://github.com/dcoai/macaw_auth/blob/v0.4.1/lib/macaw/auth/plug.ex#L1)

The request side of `Macaw.Auth`, for a Phoenix router.

    import Macaw.Auth.Plug

    pipeline :browser do
      plug :fetch_session
      plug :fetch_flash
      plug Macaw.Plug.ClientIp, trusted_proxies: [...]
      plug :fetch_current_user
    end

    pipeline :signed_in do
      plug :require_authenticated
    end

`fetch_current_user/2` assigns `:current_user`, `:current_session` and
`:pending_step` (all nil when signed out). `require_authenticated/2` sends
a signed-out visitor to the sign-in page (remembering where a `GET` was
going) and a session that owes a step to that step's page; only the
step's page and sign-out are reachable meanwhile.

The second factor's page calls `verify_second_factor/2` with the
submitted code; enrollment's page, after the backend confirms it,
`complete_step(conn, :second_factor_enroll)`.

Run `Macaw.Plug.ClientIp` before these, so a session records, and the
throttle counts, the client's address, not a proxy's. Run `fetch_flash`
before `require_role/2` and `authorize/2`, or their refusal goes home
without saying why.

**Remembering a browser.** `log_in(conn, user, remember: true)` (a
"remember me" box) also sets a signed cookie holding the session's token
(`remember_cookie/0`, `config :macaw_auth, remember_cookie:`), which
`fetch_current_user/2` reads when the session has none, after the
browser closed. It lives for the backend's idle limit, set again each
time the backend records the session's use, is `http_only`
and `same_site: "Lax"`, and is `secure` exactly when the request is
https; no setting turns that off. It dies with its session: a revoked
or expired session's token no longer resolves, and `log_out/1` deletes
it. Behind a TLS-terminating proxy, make the request's scheme the
client's (`Plug.RewriteOn` with `:x_forwarded_proto`), or the cookie goes
out without `secure`. The session cookie is the host's (its endpoint's
`Plug.Session` options); set `secure: true` there for an https site, as
this cookie does by itself.

**LiveView sockets.** `log_in/3` puts the session's `live_socket_id`
(`Macaw.Auth.live_socket_id/1`) in the session, so Phoenix names the
session's LiveView sockets by it. With `config :macaw_auth, endpoint:
MyAppWeb.Endpoint`, every revocation (`Macaw.Auth.revoke/2`, `log_out/1`)
broadcasts `"disconnect"` on it and Phoenix drops those sockets; without
`:endpoint` the hooks' own guard (`Macaw.Auth.Live`) takes the pages away.

Redirects are `Phoenix.Controller.redirect/2`'s, behind this module's own
check that the target is a path on this site (`site_path?/1`). A
remembered path came from a request, and Phoenix raises on one that
isn't safe, so anything else goes home instead: it neither leaves the
site nor fails the request.

# `authenticate`

Checks credentials as `Macaw.Auth.authenticate/2` does, passing the
client's address (`conn.remote_ip`, the client's after
`Macaw.Plug.ClientIp`) as `:remote_ip`, for the backend's throttle.

# `authorize`

Asks the app's authorizer (`Macaw.Auth.authorize/3`) whether the current
user may do `action`, and refuses as `require_role/2` does: home, with an
error flash when the pipeline ran `fetch_flash`. A signed-out visitor is
sent to sign in, as `require_role/2` sends one, remembering where a `GET`
was going. With no authorizer configured, every request is refused.

    plug :authorize, :manage
    plug :authorize, {:edit, resource: &MyAppWeb.Posts.post/1}

The resource is what the `:resource` option's function (a remote capture,
`&Mod.fun/1`, so the plug's options compile) gives of the conn, else nil.
It is asked only of a signed-in request.

# `complete_step`

Records a step the current session completed and sends it on: to the next
step, else where it was going. A session that no longer exists (revoked
meanwhile) is logged out.

# `continue`

Sends the browser to the session's next step, or where it was going.

# `fetch_current_user`

Assigns the signed-in user, session and pending step, or nils.

The token is the session's; only when the session has none is it read
from the remember cookie, and then, if it resolves, put back into the
session (renewed, with the session's `live_socket_id`) and the cookie
re-issued for another idle limit. A refused token is deleted from the
session, and a refused remembered one from the cookie too.

When the backend records the session's use on this request (its touch,
at most its interval), a remember cookie the browser sent for this
session is set again too, for a fresh idle limit: a browser kept open
past the idle limit keeps its cookie as long as its session lives. A
browser that sent none never gets one here.

# `log_in`

Starts a session for a user who passed `Macaw.Auth.authenticate/2`: the
old session is renewed (no fixation), the token stored with the
session's `live_socket_id` (`Macaw.Auth.live_socket_id/1`), and the
browser sent to the step the session owes, else where it was going,
else home.

`remember: true` also sets the remember cookie (`remember_cookie/0`):
signed, holding the token, for the backend's idle limit
(`Macaw.Auth.session_idle_seconds/1`), `http_only`, `same_site: "Lax"`,
and `secure` exactly when the request is https. Without it, a remember
cookie the browser sent is deleted. Other `opts` go to
`Macaw.Auth.create_session/2`.

When the backend refuses the session (`{:error, :invalid}`: the user no
longer exists), the sign-in fails: back to the sign-in page, with an
error flash when the pipeline ran `fetch_flash`, and neither the session
nor any cookie touched.

# `log_out`

Revokes the session (telling its open pages, and with `:endpoint`
disconnecting its LiveView sockets), renews the session, deletes the
remember cookie, and goes to sign in.

# `redirect_if_signed_in`

Sends a signed-in visitor away from the sign-in page.

# `remember_cookie`

```elixir
@spec remember_cookie() :: String.t()
```

The remember cookie's name: `config :macaw_auth, remember_cookie:`,
default `"_macaw_auth_remember"`.

# `require_authenticated`

Sends a signed-out visitor to sign in, and a session owing a step to that
step's page.

# `require_role`

Sends a user with none of `roles` home, with an error flash when the
pipeline ran `fetch_flash` (without it, home with no message). A
signed-out visitor is sent to sign in instead, remembering where a `GET`
was going, as `require_authenticated/2` does. What a role allows is the
app's to decide; this only checks membership.

# `site_path?`

Whether `path` is a path on this site that `Phoenix.Controller.redirect/2`
accepts: one leading slash, then none of a backslash, a tab or a line
break.

# `token_key`

The session key holding the token, for hosts that need it (LiveView's session).

# `verify_second_factor`

```elixir
@spec verify_second_factor(Plug.Conn.t(), term()) ::
  {:ok, Plug.Conn.t()} | {:error, atom(), Plug.Conn.t()}
```

Checks a second-factor code for the current session, passing the
client's address for the backend's throttle. On success the
`:second_factor` step is complete and the browser goes on, to the next
step or where it was going: `{:ok, conn}` (redirected, halted). On a
refusal the conn stays for the app's page to render the reason:
`{:error, reason, conn}` (`:invalid_code`, `:throttled`, or the
session's own refusal), and with no current session
`{:error, :invalid, conn}`.

    def create(conn, %{"code" => code}) do
      case Macaw.Auth.Plug.verify_second_factor(conn, code) do
        {:ok, conn} -> conn
        {:error, reason, conn} -> render(conn, :new, error: reason)
      end
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
