diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index e39f1148cf2ab15abc01810455812b0e08dab21c..b9c6fa8735c137c394d50e66b7d302d7fccf54bc 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -30,7 +30,13 @@ class GraphqlController < ApplicationController
   protect_from_forgery with: :null_session, only: :execute
 
   # must come first: current_user is set up here
-  before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
+  before_action(only: [:execute]) do
+    if Feature.enabled? :graphql_minimal_auth_methods
+      authenticate_graphql
+    else
+      authenticate_sessionless_user!(:api)
+    end
+  end
 
   before_action :authorize_access_api!
   before_action :set_user_last_activity
@@ -121,6 +127,18 @@ def feature_category
 
   private
 
+  # unwound from SessionlessAuthentication concern
+  # use a minimal subset of Gitlab::Auth::RequestAuthenticator.find_sessionless_user
+  # so only token types allowed for GraphQL can authenticate users
+  # CI_JOB_TOKENs are not allowed for now, since their access is too broad
+  def authenticate_graphql
+    user = request_authenticator.find_user_from_web_access_token(:api, scopes: [:api, :read_api])
+    user ||= request_authenticator.find_user_from_personal_access_token_for_api_or_git
+    sessionless_sign_in(user) if user
+  rescue Gitlab::Auth::AuthenticationError
+    nil
+  end
+
   def permitted_multiplex_params
     params.permit(_json: [:query, :operationName, { variables: {} }])
   end
diff --git a/config/feature_flags/gitlab_com_derisk/graphql_minimal_auth_methods.yml b/config/feature_flags/gitlab_com_derisk/graphql_minimal_auth_methods.yml
new file mode 100644
index 0000000000000000000000000000000000000000..afdfa1b65c4ad74cc8f891d2606737cc77a1bfc3
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/graphql_minimal_auth_methods.yml
@@ -0,0 +1,9 @@
+---
+name: graphql_minimal_auth_methods
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/438462
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150407
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/444929
+milestone: '17.0'
+group: group::authentication
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index bdc3d0acdf7e544b56f32984f0316bafaf854ac6..0ae74a36f6ac7078ba0533a8f67bd80651adb852 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -262,6 +262,215 @@
         expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
       end
 
+      shared_examples 'valid token' do
+        it 'accepts from header' do
+          post_graphql(query, headers: { 'Authorization' => "Bearer #{token}" })
+
+          expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+        end
+
+        it 'accepts from access_token parameter' do
+          post "/api/graphql?access_token=#{token}", params: { query: query }
+
+          expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+        end
+
+        it 'accepts from private_token parameter' do
+          post "/api/graphql?private_token=#{token}", params: { query: query }
+
+          expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+        end
+      end
+
+      context 'with oAuth user access token' do
+        let(:oauth_application) do
+          create(
+            :oauth_application,
+            scopes: 'api read_user',
+            redirect_uri: 'http://example.com',
+            confidential: true
+          )
+        end
+
+        let(:oauth_access_token) do
+          create(
+            :oauth_access_token,
+            application: oauth_application,
+            resource_owner: user,
+            scopes: 'api'
+          )
+        end
+
+        let(:token) { oauth_access_token.plaintext_token }
+
+        # Doorkeeper does not support the private_token=? param
+        # https://github.com/doorkeeper-gem/doorkeeper/blob/960f1501131683b16c2704d1b6f9597b9583b49d/lib/doorkeeper/oauth/token.rb#L26
+        # so we cannot use shared examples here
+        it 'accepts from header' do
+          post_graphql(query, headers: { 'Authorization' => "Bearer #{token}" })
+
+          expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+        end
+
+        it 'accepts from access_token parameter' do
+          post "/api/graphql?access_token=#{token}", params: { query: query }
+
+          expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+        end
+      end
+
+      context 'with personal access token' do
+        let(:personal_access_token) { create(:personal_access_token, user: user) }
+        let(:token) { personal_access_token.token }
+
+        it_behaves_like 'valid token'
+      end
+
+      context 'with group or project access token' do
+        let_it_be(:user) { create(:user, :project_bot) }
+        let_it_be(:project_access_token) { create(:personal_access_token, user: user) }
+
+        let(:token) { project_access_token.token }
+
+        it_behaves_like 'valid token'
+      end
+
+      describe 'invalid authentication types' do
+        let(:query) { 'query { currentUser { id, username } }' }
+
+        describe 'with git-lfs token' do
+          let(:lfs_token) { Gitlab::LfsToken.new(user).token }
+          let(:header_token) { Base64.encode64("#{user.username}:#{lfs_token}") }
+          let(:headers) do
+            { 'Authorization' => "Basic #{header_token}" }
+          end
+
+          it 'does not authenticate users with an LFS token' do
+            post '/api/graphql.git', params: { query: query }, headers: headers
+
+            expect(graphql_data['currentUser']).to be_nil
+          end
+
+          context 'when graphql_minimal_auth_methods FF is disabled' do
+            before do
+              stub_feature_flags(graphql_minimal_auth_methods: false)
+            end
+
+            it 'authenticates users with an LFS token' do
+              post '/api/graphql.git', params: { query: query }, headers: headers
+
+              expect(graphql_data['currentUser']['username']).to eq(user.username)
+            end
+          end
+        end
+
+        describe 'with job token' do
+          let(:project) do
+            create(:project).tap do |proj|
+              proj.add_owner(user)
+            end
+          end
+
+          let(:job) { create(:ci_build, :running, project: project, user: user) }
+          let(:job_token) { job.token }
+
+          it 'raises "Invalid token" error' do
+            post '/api/graphql', params: { query: query, job_token: job_token }
+
+            expect_graphql_errors_to_include(/Invalid token/)
+          end
+
+          context 'when graphql_minimal_auth_methods FF is disabled' do
+            before do
+              stub_feature_flags(graphql_minimal_auth_methods: false)
+            end
+
+            it 'authenticates as the user' do
+              post '/api/graphql', params: { query: query, job_token: job_token }
+
+              expect(graphql_data['currentUser']['username']).to eq(user.username)
+            end
+          end
+        end
+
+        describe 'with static object token' do
+          let(:headers) do
+            { 'X-Gitlab-Static-Object-Token' => user.static_object_token }
+          end
+
+          it 'does not authenticate user from header' do
+            post '/api/graphql', params: { query: query }, headers: headers
+
+            expect(graphql_data['currentUser']).to be_nil
+          end
+
+          it 'does not authenticate user from parameter' do
+            post "/api/graphql?token=#{user.static_object_token}", params: { query: query }
+
+            expect_graphql_errors_to_include(/Invalid token/)
+          end
+
+          # context is included to demonstrate that the FF code is not changing this behavior
+          context 'when graphql_minimal_auth_methods FF is disabled' do
+            before do
+              stub_feature_flags(graphql_minimal_auth_methods: false)
+            end
+
+            # expect(graphql_data['currentUser']).to be_nil
+            it 'does not authenticate user from header' do
+              post '/api/graphql', params: { query: query }, headers: headers
+
+              expect(graphql_data['currentUser']).to be_nil
+            end
+
+            it 'does not authenticate user from parameter' do
+              post "/api/graphql?token=#{user.static_object_token}", params: { query: query }
+
+              expect_graphql_errors_to_include(/Invalid token/)
+            end
+          end
+        end
+
+        describe 'with dependency proxy token' do
+          include DependencyProxyHelpers
+          let(:token) { build_jwt(user).encoded }
+          let(:headers) do
+            { 'Authorization' => "Bearer #{token}" }
+          end
+
+          it 'does not authenticate user from dependency proxy token in headers' do
+            post '/api/graphql', params: { query: query }, headers: headers
+
+            expect_graphql_errors_to_include(/Invalid token/)
+          end
+
+          it 'does not authenticate user from dependency proxy token in parameter' do
+            post "/api/graphql?access_token=#{token}", params: { query: query }
+
+            expect_graphql_errors_to_include(/Invalid token/)
+          end
+
+          # context is included to demonstrate that the FF code is not changing this behavior
+          context 'when graphql_minimal_auth_methods FF is disabled' do
+            before do
+              stub_feature_flags(graphql_minimal_auth_methods: false)
+            end
+
+            it 'does not authenticate user from dependency proxy token in headers' do
+              post '/api/graphql', params: { query: query }, headers: headers
+
+              expect_graphql_errors_to_include(/Invalid token/)
+            end
+
+            it 'does not authenticate user from dependency proxy token in parameter' do
+              post "/api/graphql?access_token=#{token}", params: { query: query }
+
+              expect_graphql_errors_to_include(/Invalid token/)
+            end
+          end
+        end
+      end
+
       it 'prevents access by deactived users' do
         token.user.deactivate!