diff --git a/lib/pleroma/plugs/auth_expected_plug.ex b/lib/pleroma/plugs/auth_expected_plug.ex
new file mode 100644
index 000000000..9e4a4bec8
--- /dev/null
+++ b/lib/pleroma/plugs/auth_expected_plug.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.AuthExpectedPlug do
+  import Plug.Conn
+
+  def init(options), do: options
+
+  def call(conn, _) do
+    put_private(conn, :auth_expected, true)
+  end
+end
diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex
index 38df074ad..b09e1bb4d 100644
--- a/lib/pleroma/plugs/oauth_scopes_plug.ex
+++ b/lib/pleroma/plugs/oauth_scopes_plug.ex
@@ -8,12 +8,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
 
   alias Pleroma.Config
   alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
+  alias Pleroma.Plugs.PlugHelper
 
   @behaviour Plug
 
   def init(%{scopes: _} = options), do: options
 
   def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
+    conn = PlugHelper.append_to_called_plugs(conn, __MODULE__)
+
     op = options[:op] || :|
     token = assigns[:token]
 
diff --git a/lib/pleroma/plugs/plug_helper.ex b/lib/pleroma/plugs/plug_helper.ex
new file mode 100644
index 000000000..4f83e9414
--- /dev/null
+++ b/lib/pleroma/plugs/plug_helper.ex
@@ -0,0 +1,38 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.PlugHelper do
+  @moduledoc "Pleroma Plug helper"
+
+  def append_to_called_plugs(conn, plug_module) do
+    append_to_private_list(conn, :called_plugs, plug_module)
+  end
+
+  def append_to_skipped_plugs(conn, plug_module) do
+    append_to_private_list(conn, :skipped_plugs, plug_module)
+  end
+
+  def plug_called?(conn, plug_module) do
+    contained_in_private_list?(conn, :called_plugs, plug_module)
+  end
+
+  def plug_skipped?(conn, plug_module) do
+    contained_in_private_list?(conn, :skipped_plugs, plug_module)
+  end
+
+  def plug_called_or_skipped?(conn, plug_module) do
+    plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
+  end
+
+  defp append_to_private_list(conn, private_variable, value) do
+    list = conn.private[private_variable] || []
+    modified_list = Enum.uniq(list ++ [value])
+    Plug.Conn.put_private(conn, private_variable, modified_list)
+  end
+
+  defp contained_in_private_list?(conn, private_variable, value) do
+    list = conn.private[private_variable] || []
+    value in list
+  end
+end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index 43649ad26..557cde328 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastoFEController do
     when action == :index
   )
 
-  plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
+  plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :manifest])
 
   @doc "GET /web/*path"
   def index(%{assigns: %{user: user, token: token}} = conn, _params)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 21bc3d5a5..bd6853d12 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -15,10 +15,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.MastodonAPI.ListView
   alias Pleroma.Web.MastodonAPI.MastodonAPI
+  alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
+  plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs)
+
   plug(
     OAuthScopesPlug,
     %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
@@ -369,6 +372,8 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
   end
 
   @doc "GET /api/v1/endorsements"
-  def endorsements(conn, params),
-    do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
+  def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
+
+  @doc "GET /api/v1/identity_proofs"
+  def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 14075307d..ac8c18f24 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -3,21 +3,31 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
+  @moduledoc """
+  Contains stubs for unimplemented Mastodon API endpoints.
+
+  Note: instead of routing directly to this controller's action,
+    it's preferable to define an action in relevant (non-generic) controller,
+    set up OAuth rules for it and call this controller's function from it.
+  """
+
   use Pleroma.Web, :controller
 
   require Logger
 
+  plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object])
+
+  plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
+
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
-  # Stubs for unimplemented mastodon api
-  #
   def empty_array(conn, _) do
-    Logger.debug("Unimplemented, returning an empty array")
+    Logger.debug("Unimplemented, returning an empty array (list)")
     json(conn, [])
   end
 
   def empty_object(conn, _) do
-    Logger.debug("Unimplemented, returning an empty object")
+    Logger.debug("Unimplemented, returning an empty object (map)")
     json(conn, %{})
   end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
index 0cdc7bd8d..c93a43969 100644
--- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -5,10 +5,13 @@
 defmodule Pleroma.Web.MastodonAPI.SuggestionController do
   use Pleroma.Web, :controller
 
+  alias Pleroma.Plugs.OAuthScopesPlug
+
   require Logger
 
+  plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
+
   @doc "GET /api/v1/suggestions"
-  def index(conn, _) do
-    json(conn, [])
-  end
+  def index(conn, params),
+    do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
 end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 46688db7e..0121cd661 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -27,6 +27,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   plug(:fetch_flash)
   plug(RateLimiter, [name: :authentication] when action == :create_authorization)
 
+  plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug)
+
   action_fallback(Pleroma.Web.OAuth.FallbackController)
 
   @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index dae7f0f2f..75f61b675 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["write:conversations"]} when action == :update_conversation
+    %{scopes: ["write:conversations"]} when action in [:update_conversation, :read_conversations]
   )
 
   plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 5a0902739..3d57073d0 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -34,6 +34,7 @@ defmodule Pleroma.Web.Router do
   pipeline :authenticated_api do
     plug(:accepts, ["json"])
     plug(:fetch_session)
+    plug(Pleroma.Plugs.AuthExpectedPlug)
     plug(Pleroma.Plugs.OAuthPlug)
     plug(Pleroma.Plugs.BasicAuthDecoderPlug)
     plug(Pleroma.Plugs.UserFetcherPlug)
@@ -333,7 +334,7 @@ defmodule Pleroma.Web.Router do
     get("/accounts/relationships", AccountController, :relationships)
 
     get("/accounts/:id/lists", AccountController, :lists)
-    get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
+    get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
 
     get("/follow_requests", FollowRequestController, :index)
     get("/blocks", AccountController, :blocks)
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 0229aea97..31adc2817 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
 
+  plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
+
   plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
 
   action_fallback(:errors)
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index cf3ac1287..1af29ce78 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -29,11 +29,34 @@ def controller do
       import Pleroma.Web.Router.Helpers
       import Pleroma.Web.TranslationHelpers
 
+      alias Pleroma.Plugs.PlugHelper
+
       plug(:set_put_layout)
 
       defp set_put_layout(conn, _) do
         put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
       end
+
+      # Marks a plug as intentionally skipped
+      #   (states that the plug is not called for a good reason, not by a mistake)
+      defp skip_plug(conn, plug_module) do
+        PlugHelper.append_to_skipped_plugs(conn, plug_module)
+      end
+
+      # Here we can apply before-action hooks (e.g. verify whether auth checks were preformed)
+      defp action(conn, params) do
+        if conn.private[:auth_expected] &&
+             not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
+          conn
+          |> render_error(
+            :forbidden,
+            "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+          )
+          |> halt()
+        else
+          super(conn, params)
+        end
+      end
     end
   end
 
diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs
index c697a39f8..8d0e70db8 100644
--- a/test/web/mastodon_api/controllers/suggestion_controller_test.exs
+++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs
@@ -7,34 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
 
   alias Pleroma.Config
 
-  import Pleroma.Factory
-  import Tesla.Mock
-
   setup do: oauth_access(["read"])
 
-  setup %{user: user} do
-    other_user = insert(:user)
-    host = Config.get([Pleroma.Web.Endpoint, :url, :host])
-    url500 = "http://test500?#{host}&#{user.nickname}"
-    url200 = "http://test200?#{host}&#{user.nickname}"
-
-    mock(fn
-      %{method: :get, url: ^url500} ->
-        %Tesla.Env{status: 500, body: "bad request"}
-
-      %{method: :get, url: ^url200} ->
-        %Tesla.Env{
-          status: 200,
-          body:
-            ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{
-              other_user.ap_id
-            }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}])
-        }
-    end)
-
-    [other_user: other_user]
-  end
-
   test "returns empty result", %{conn: conn} do
     res =
       conn
diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
index 32250f06f..8f0cbe9b2 100644
--- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
+++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
@@ -203,7 +203,7 @@ test "PATCH /api/v1/pleroma/conversations/:id" do
 
   test "POST /api/v1/pleroma/conversations/read" do
     user = insert(:user)
-    %{user: other_user, conn: conn} = oauth_access(["write:notifications"])
+    %{user: other_user, conn: conn} = oauth_access(["write:conversations"])
 
     {:ok, _activity} =
       CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"})