AshAuthentication confirmation page not using correct layout

The page served for /auth/user/confirm_new_user?confirm=longtokenhere does not appear to be using my application's layout template, because it does not have any of the elements in <head> that the rest of my app has. My router looks like this:
defmodule MyAppWeb.Router do
use MyAppWeb, :router
use AshAuthentication.Phoenix.Router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_app_browser_headers
plug :load_from_session
end

scope "/", MyAppWeb do
pipe_through :browser

get "/", PageController, :home

ash_authentication_live_session :authentication_required,
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox), do: MyAppWeb.LiveAllowEctoSandbox),
on_mount: [{MyAppWeb.LiveUserAuth, :live_user_required}] do
# ...
end

ash_authentication_live_session :authentication_optional,
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox), do: MyAppWeb.LiveAllowEctoSandbox),
on_mount: [{MyAppWeb.LiveUserAuth, :live_user_optional}] do
# ...
end

confirm_route MyApp.Accounts.User, :confirm_new_user, auth_routes_prefix: "/auth"
auth_routes AuthController, MyApp.Accounts.User, path: "/auth"
sign_out_route AuthController

sign_in_route register_path: "/register",
reset_path: "/reset",
auth_routes_prefix: "/auth",
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox),
do: MyAppWeb.LiveAllowEctoSandbox
),
on_mount: [{MyAppWeb.LiveUserAuth, :live_no_user}]

reset_route auth_routes_prefix: "/auth"
end

# ...
end
defmodule MyAppWeb.Router do
use MyAppWeb, :router
use AshAuthentication.Phoenix.Router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_app_browser_headers
plug :load_from_session
end

scope "/", MyAppWeb do
pipe_through :browser

get "/", PageController, :home

ash_authentication_live_session :authentication_required,
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox), do: MyAppWeb.LiveAllowEctoSandbox),
on_mount: [{MyAppWeb.LiveUserAuth, :live_user_required}] do
# ...
end

ash_authentication_live_session :authentication_optional,
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox), do: MyAppWeb.LiveAllowEctoSandbox),
on_mount: [{MyAppWeb.LiveUserAuth, :live_user_optional}] do
# ...
end

confirm_route MyApp.Accounts.User, :confirm_new_user, auth_routes_prefix: "/auth"
auth_routes AuthController, MyApp.Accounts.User, path: "/auth"
sign_out_route AuthController

sign_in_route register_path: "/register",
reset_path: "/reset",
auth_routes_prefix: "/auth",
on_mount_prepend:
if(Application.compile_env(:my_app, :sandbox),
do: MyAppWeb.LiveAllowEctoSandbox
),
on_mount: [{MyAppWeb.LiveUserAuth, :live_no_user}]

reset_route auth_routes_prefix: "/auth"
end

# ...
end
Solution:
If I explicitly pass path: "/auth/user/", then the root layout is used. I'm now thinking the URL I send in the email is incorrect.
Jump to solution
12 Replies
Rebecca Le
Rebecca Le4mo ago
thats correct, it uses its own layout
aidalgol
aidalgolOP4mo ago
OK, then how is it meant to get the CSRF token? The root layout has this:
<meta name="csrf-token" content={get_csrf_token()} />
<meta name="csrf-token" content={get_csrf_token()} />
Without that, submitting the form raises a Plug.CSRFProtection.InvalidCSRFTokenError
Rebecca Le
Rebecca Le4mo ago
your router appears to be missing the put_secure_browser_headers plug - that might be what does it
aidalgol
aidalgolOP4mo ago
:put_app_browser_headers is my own wrapper that calls that plug.
defp put_app_browser_headers(conn, _opts) do
%{scheme: scheme, host: host, port: port} =
ExAws.Config.new(:s3)
|> ExAws.Config.retrieve_runtime_config()

s3_base_url = scheme <> host <> ":" <> to_string(port) <> "/"

content_security_policy = %{
# Only allow 'self' by default for all resource types.
"default-src" => ["'self'"],
"img-src" => [
# Allow images from the application's static assets.
"'self'",
# Allow data image URLs to allow CSS-embedded SVG icons.
"data:",
# Allow blob for image upload previews.
"blob:",
# Allow images from our S3 bucket.
s3_base_url,
# Allow images from Ash's domain.
"https://ash-hq.org/images/"
],
"connect-src" => [
# Allow connections to the Phoenix server (primarily LiveView).
"'self'",
# Allow uploading to S3.
s3_base_url
],
"script-src" => [
# Allow WebAssembly
"'wasm-unsafe-eval'"
],
"script-src-elem" =>
[
# Allow app.js
"'self'"
] ++
if Application.get_env(:my_app, :dev_routes) do
# Required by the dev mailbox viewer.
["'unsafe-inline'"]
else
[]
end
}

put_secure_browser_headers(conn, %{
"content-security-policy" =>
content_security_policy
|> Enum.map_join("; ", fn {k, vs} -> k <> " " <> (vs |> Enum.join(" ")) end)
})
end
defp put_app_browser_headers(conn, _opts) do
%{scheme: scheme, host: host, port: port} =
ExAws.Config.new(:s3)
|> ExAws.Config.retrieve_runtime_config()

s3_base_url = scheme <> host <> ":" <> to_string(port) <> "/"

content_security_policy = %{
# Only allow 'self' by default for all resource types.
"default-src" => ["'self'"],
"img-src" => [
# Allow images from the application's static assets.
"'self'",
# Allow data image URLs to allow CSS-embedded SVG icons.
"data:",
# Allow blob for image upload previews.
"blob:",
# Allow images from our S3 bucket.
s3_base_url,
# Allow images from Ash's domain.
"https://ash-hq.org/images/"
],
"connect-src" => [
# Allow connections to the Phoenix server (primarily LiveView).
"'self'",
# Allow uploading to S3.
s3_base_url
],
"script-src" => [
# Allow WebAssembly
"'wasm-unsafe-eval'"
],
"script-src-elem" =>
[
# Allow app.js
"'self'"
] ++
if Application.get_env(:my_app, :dev_routes) do
# Required by the dev mailbox viewer.
["'unsafe-inline'"]
else
[]
end
}

put_secure_browser_headers(conn, %{
"content-security-policy" =>
content_security_policy
|> Enum.map_join("; ", fn {k, vs} -> k <> " " <> (vs |> Enum.join(" ")) end)
})
end
Rebecca Le
Rebecca Le4mo ago
ah hah
aidalgol
aidalgolOP4mo ago
Is there something missing?
Rebecca Le
Rebecca Le4mo ago
not sure. there definitely shouldn't be any CSRF error
ZachDaniel
ZachDaniel4mo ago
is this on phoenix 1.18? Maybe their new layout changes cause problems and we have to accept some kind of "layout component" option?
aidalgol
aidalgolOP4mo ago
No, still 1.17. SignInLive and ResetLive appear to be using my app's "root" layout, but ConfirmLive is not for whatever reason, which seems wrong. I think I might see the problem. The generated route is
auth_path GET /confirm_new_user/:token AshAuthentication.Phoenix.ConfirmLive :confirm
auth_path GET /confirm_new_user/:token AshAuthentication.Phoenix.ConfirmLive :confirm
Which is not under /auth, which is what is passed via opts[:auth_routes_prefix]. I think the first argument to scope in the confirm_route macro may be incorrect. https://github.com/team-alembic/ash_authentication_phoenix/blob/5f05b73b2d2b36f77ed53377d0e91989c523d319/lib/ash_authentication_phoenix/router.ex#L536
scope unquote(path), unquote(opts) do
scope unquote(path), unquote(opts) do
path is "/#{strategy}", which would be "/confirm_new_user".
Solution
aidalgol
aidalgol4mo ago
If I explicitly pass path: "/auth/user/", then the root layout is used. I'm now thinking the URL I send in the email is incorrect.
aidalgol
aidalgolOP4mo ago
Right, so everything under /auth is more like API endpoints for AshAuthentication, and the LiveView routes should be outside that, going by the routes in a base AAP setup.
auth_path GET /sign-in AshAuthentication.Phoenix.SignInLive :sign_in
auth_reset_path GET /reset AshAuthentication.Phoenix.SignInLive :reset
auth_register_path GET /register AshAuthentication.Phoenix.SignInLive :register
auth_path GET /sign-in AshAuthentication.Phoenix.SignInLive :sign_in
auth_reset_path GET /reset AshAuthentication.Phoenix.SignInLive :reset
auth_register_path GET /register AshAuthentication.Phoenix.SignInLive :register
I'm submitting a PR to correct the Confirmation Tutorial. But why is anything under /auth serving a human-readable HTML page when I visit it in the browser?
aidalgol
aidalgolOP4mo ago
GitHub
docs: correct route in sample confirmation email by aidalgol · Pul...
In the Confirmation Tutorial, the example mailer used the wrong route. AshAuthenticationPhoenix LiveView pages are served outside the auth routes prefix.

Did you find this page helpful?