From 6fec09207a4ae0f28c4084f3639036c96ce68a46 Mon Sep 17 00:00:00 2001 From: Pascal Knoth Date: Sat, 12 Apr 2025 11:28:57 +0200 Subject: [PATCH 1/2] [ssi] verifiable presentation CSRF protection --- apps/boruta_admin/assets/src/views/Home.vue | 19 +++++++++++-- .../templates/page/admin.html.eex | 6 ++++ .../lib/boruta_identity_web/router.ex | 7 ++--- .../controllers/oauth/authorize_controller.ex | 28 ++++++++++++++++++- apps/boruta_web/lib/boruta_web/router.ex | 1 + 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/boruta_admin/assets/src/views/Home.vue b/apps/boruta_admin/assets/src/views/Home.vue index 2013b0c93..01733ef05 100644 --- a/apps/boruta_admin/assets/src/views/Home.vue +++ b/apps/boruta_admin/assets/src/views/Home.vue @@ -69,7 +69,16 @@

Verifiable credential presentation

- Trigger example presentation with associated boruta wallet (issue example credential first) +
+ + + + + + + + +
@@ -81,13 +90,19 @@ export default { name: 'home', computed: { + csrf_token () { + return window.env.CSRF_TOKEN + }, walletUrl () { return window.env.BORUTA_OAUTH_BASE_URL + '/accounts/wallet' }, presentationUrl () { return window.env.BORUTA_OAUTH_BASE_URL + - `/oauth/authorize?client_id=00000000-0000-0000-0000-000000000001&redirect_uri=${window.env.BORUTA_OAUTH_BASE_URL}/accounts/wallet/verifiable-presentation&scope=BorutaCredentialJwtVc&response_type=vp_token&client_metadata={}&state=qrm0c4xm&prompt=login` + `/oauth/authorize` + }, + presentationRedirectUri () { + return `${window.env.BORUTA_OAUTH_BASE_URL}/accounts/wallet/verifiable-presentation` }, preauthorizeUrl () { return window.env.BORUTA_OAUTH_BASE_URL + diff --git a/apps/boruta_admin/lib/boruta_admin_web/templates/page/admin.html.eex b/apps/boruta_admin/lib/boruta_admin_web/templates/page/admin.html.eex index 0a4db11af..b91b00717 100644 --- a/apps/boruta_admin/lib/boruta_admin_web/templates/page/admin.html.eex +++ b/apps/boruta_admin/lib/boruta_admin_web/templates/page/admin.html.eex @@ -13,11 +13,17 @@ BORUTA_ADMIN_OAUTH_BASE_URL: '<%= System.get_env("BORUTA_ADMIN_OAUTH_BASE_URL", "http://localhost:4000") %>', BORUTA_ADMIN_BASE_URL: '<%= System.get_env("BORUTA_ADMIN_BASE_URL", "http://localhost:4001") %>', BORUTA_OAUTH_BASE_URL: '<%= System.get_env("BORUTA_OAUTH_BASE_URL", "http://localhost:4000") %>', + CSRF_TOKEN: '<%= Phoenix.Controller.get_csrf_token() %>' } " media="all"/> + <%= if get_flash(@conn, :error) do %> +
+ <%= get_flash(@conn, :error) %> +
+ <% end %> diff --git a/apps/boruta_identity/lib/boruta_identity_web/router.ex b/apps/boruta_identity/lib/boruta_identity_web/router.ex index bfccd109c..ff48dda6b 100644 --- a/apps/boruta_identity/lib/boruta_identity_web/router.ex +++ b/apps/boruta_identity/lib/boruta_identity_web/router.ex @@ -86,16 +86,13 @@ defmodule BorutaIdentityWeb.Router do @impl Plug.ErrorHandler def handle_errors(conn, %{reason: %Plug.CSRFProtection.InvalidCSRFTokenError{message: message}}) do - with [referer] <- Plug.Conn.get_req_header(conn, "referer"), - %URI{path: path, query: query} <- URI.parse(referer) do - uri = %URI{path: path, query: query} - + with [referer] <- Plug.Conn.get_req_header(conn, "referer") do conn |> Plug.Conn.fetch_session() |> Phoenix.Controller.fetch_flash() |> Phoenix.Controller.put_flash(:error, message) |> Plug.Conn.put_status(:found) - |> Phoenix.Controller.redirect(to: URI.to_string(uri)) + |> Phoenix.Controller.redirect(external: referer) else _ -> render_error(conn, message) diff --git a/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex b/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex index 1085f4ba9..64d129ef2 100644 --- a/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex +++ b/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex @@ -31,12 +31,13 @@ defmodule BorutaWeb.Oauth.AuthorizeController do alias BorutaIdentityWeb.Router.Helpers, as: IdentityRoutes alias BorutaIdentityWeb.TemplateView - def authorize(%Plug.Conn{} = conn, _params) do + def authorize(%Plug.Conn{} = conn, params) do current_user = conn.assigns[:current_user] conn = put_unsigned_request(conn) with {:unchanged, conn} <- prompt_redirection(conn, current_user), + {:unchanged, conn} <- check_method(conn, params), {:unchanged, conn} <- public_client?(conn), {:unchanged, conn} <- verifiable_presentation?(conn), {:unchanged, conn} <- max_age_redirection(conn, current_user), @@ -59,11 +60,36 @@ defmodule BorutaWeb.Oauth.AuthorizeController do {:authorize, conn} -> conn + {:error, conn} -> + conn + {:redirected, conn} -> conn end end + def check_method(%Plug.Conn{method: "POST"} = conn, %{"response_type" => "vp_token"}) do + {:unchanged, %{conn | query_params: Map.merge(conn.query_params, conn.body_params)}} + end + + def check_method(%Plug.Conn{method: "GET"} = conn, %{"response_type" => "vp_token"}) do + {:error, authorize_error(conn, %Error{ + status: :bad_request, + error: :invalid_request, + error_description: "Presentation requests requires POST method" + })} + end + + def check_method(%Plug.Conn{method: "GET"} = conn, _params), do: {:unchanged, conn} + + def check_method(%Plug.Conn{method: "POST"} = conn, %{"response_type" => response_type}) do + {:error, authorize_error(conn, %Error{ + status: :bad_request, + error: :invalid_request, + error_description: "#{response_type} requests require GET method" + })} + end + def public_client?(conn) do case conn.query_params["client_id"] do "did:" <> _key -> diff --git a/apps/boruta_web/lib/boruta_web/router.ex b/apps/boruta_web/lib/boruta_web/router.ex index c0a0e320d..fdb0f3dea 100644 --- a/apps/boruta_web/lib/boruta_web/router.ex +++ b/apps/boruta_web/lib/boruta_web/router.ex @@ -77,6 +77,7 @@ defmodule BorutaWeb.Router do pipe_through([:browser, :fetch_current_user]) get("/authorize", AuthorizeController, :authorize) + post("/authorize", AuthorizeController, :authorize) end scope "/did", BorutaWeb do From 4cb4af2ceff5e0f787311437d02b6beb95e8d849 Mon Sep 17 00:00:00 2001 From: Pascal Knoth Date: Sat, 19 Apr 2025 20:14:50 +0200 Subject: [PATCH 2/2] [ssi] enable prompt redirection on post authorization --- .../lib/boruta_identity_web/router.ex | 16 ++++++++-------- .../controllers/oauth/authorize_controller.ex | 4 ++-- .../controllers/oauth/openid_connect_test.exs | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/boruta_identity/lib/boruta_identity_web/router.ex b/apps/boruta_identity/lib/boruta_identity_web/router.ex index ff48dda6b..34874abe9 100644 --- a/apps/boruta_identity/lib/boruta_identity_web/router.ex +++ b/apps/boruta_identity/lib/boruta_identity_web/router.ex @@ -86,14 +86,14 @@ defmodule BorutaIdentityWeb.Router do @impl Plug.ErrorHandler def handle_errors(conn, %{reason: %Plug.CSRFProtection.InvalidCSRFTokenError{message: message}}) do - with [referer] <- Plug.Conn.get_req_header(conn, "referer") do - conn - |> Plug.Conn.fetch_session() - |> Phoenix.Controller.fetch_flash() - |> Phoenix.Controller.put_flash(:error, message) - |> Plug.Conn.put_status(:found) - |> Phoenix.Controller.redirect(external: referer) - else + case Plug.Conn.get_req_header(conn, "referer") do + [referer] -> + conn + |> Plug.Conn.fetch_session() + |> Phoenix.Controller.fetch_flash() + |> Phoenix.Controller.put_flash(:error, message) + |> Plug.Conn.put_status(:found) + |> Phoenix.Controller.redirect(external: referer) _ -> render_error(conn, message) end diff --git a/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex b/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex index 64d129ef2..5e25a0459 100644 --- a/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex +++ b/apps/boruta_web/lib/boruta_web/controllers/oauth/authorize_controller.ex @@ -36,8 +36,8 @@ defmodule BorutaWeb.Oauth.AuthorizeController do conn = put_unsigned_request(conn) - with {:unchanged, conn} <- prompt_redirection(conn, current_user), - {:unchanged, conn} <- check_method(conn, params), + with {:unchanged, conn} <- check_method(conn, params), + {:unchanged, conn} <- prompt_redirection(conn, current_user), {:unchanged, conn} <- public_client?(conn), {:unchanged, conn} <- verifiable_presentation?(conn), {:unchanged, conn} <- max_age_redirection(conn, current_user), diff --git a/apps/boruta_web/test/boruta_web/controllers/oauth/openid_connect_test.exs b/apps/boruta_web/test/boruta_web/controllers/oauth/openid_connect_test.exs index 1f90af4b5..033ee7fe7 100644 --- a/apps/boruta_web/test/boruta_web/controllers/oauth/openid_connect_test.exs +++ b/apps/boruta_web/test/boruta_web/controllers/oauth/openid_connect_test.exs @@ -70,7 +70,7 @@ defmodule BorutaWeb.Integration.OpenidConnectTest do redirect_uri: redirect_uri } do conn = - get( + post( conn, Routes.authorize_path(conn, :authorize, %{ response_type: "vp_token",