chain of auth
diff --git a/app_dart/bin/gae_server.dart b/app_dart/bin/gae_server.dart
index ad41734..187be7e 100644
--- a/app_dart/bin/gae_server.dart
+++ b/app_dart/bin/gae_server.dart
@@ -61,17 +61,31 @@
     configUpdater.startUpdateLoop(config);
 
     final firebaseJwtValidator = FirebaseJwtValidator(cache: cache);
-    final dashboardAuthProvider = DashboardAuthentication(
+
+    const cronAuthentication = DashboardCronAuthentication();
+    final firebaseAuthentication = DashboardFirebaseAuthentication(
       cache: cache,
-      firebaseJwtValidator: firebaseJwtValidator,
+      validator: firebaseJwtValidator,
       firestore: firestore,
     );
-    final presubmitAuthProvider = PresubmitAuthentication(
+    final githubAuthentication = GithubAuthentication(
       cache: cache,
       config: config,
-      firebaseJwtValidator: firebaseJwtValidator,
-      firestore: firestore,
+      validator: firebaseJwtValidator,
     );
+
+    final cronAuthProvider = ChainOfAuthentication.forProviders([
+      cronAuthentication,
+    ]);
+    final dashboardAuthProvider = ChainOfAuthentication.forProviders([
+      cronAuthentication,
+      firebaseAuthentication,
+    ]);
+    final presubmitAuthProvider = ChainOfAuthentication.forProviders([
+      firebaseAuthentication,
+      githubAuthentication,
+    ]);
+
     final AuthenticationProvider swarmingAuthProvider =
         SwarmingAuthenticationProvider(config: config);
 
@@ -146,7 +160,7 @@
       bigQuery: bigQuery,
       cache: cache,
       dashboardAuthProvider: dashboardAuthProvider,
-      cronAuthProvider: dashboardAuthProvider.onlyCron,
+      cronAuthProvider: cronAuthProvider,
       presubmitAuthProvider: presubmitAuthProvider,
       branchService: branchService,
       buildBucketClient: buildBucketClient,
diff --git a/app_dart/lib/src/request_handling/dashboard_authentication.dart b/app_dart/lib/src/request_handling/dashboard_authentication.dart
index 2987119..38f8e04 100644
--- a/app_dart/lib/src/request_handling/dashboard_authentication.dart
+++ b/app_dart/lib/src/request_handling/dashboard_authentication.dart
@@ -15,9 +15,9 @@
 import '../service/firebase_jwt_validator.dart';
 import 'exceptions.dart';
 
-/// Class capable of authenticating [Request]s from the Dashboard
+/// Class capable of authenticating [Request]s from multiple sources.
 ///
-/// There are two types of authentication this class supports:
+/// There are three types of authentication this class can chain:
 ///
 ///  1. If the request has the `'X-Appengine-Cron'` HTTP header set to "true",
 ///     then the request will be authenticated as an App Engine cron job.
@@ -37,6 +37,9 @@
 ///     User accounts are only authorized if the user is either a "@google.com"
 ///     account or is an [AllowedAccount] in Cocoon's Firestore.
 ///
+///  3. If the request has github.com token, then the request will be authenticated
+///     as a GitHub user account.
+///
 /// If none of the above authentication methods yield an authenticated
 /// request, then the request is unauthenticated, and any call to
 /// [authenticate] will throw an [Unauthenticated] exception.
@@ -45,41 +48,10 @@
 ///
 ///  * <https://cloud.google.com/appengine/docs/standard/python/reference/request-response-headers>
 @immutable
-interface class DashboardAuthentication implements AuthenticationProvider {
-  factory DashboardAuthentication({
-    required CacheService cache,
-    required FirebaseJwtValidator firebaseJwtValidator,
-    required FirestoreService firestore,
-    ClientContextProvider clientContextProvider = Providers.serviceScopeContext,
-    HttpClientProvider httpClientProvider = Providers.freshHttpClient,
-  }) {
-    return DashboardAuthentication.forProviders(
-      authProviders: [
-        DashboardCronAuthentication(
-          clientContextProvider: clientContextProvider,
-        ),
-        DashboardFirebaseAuthentication(
-          cache: cache,
-          validator: firebaseJwtValidator,
-          clientContextProvider: clientContextProvider,
-          firestore: firestore,
-        ),
-      ],
-    );
-  }
-
+interface class ChainOfAuthentication implements AuthenticationProvider {
   /// Creates an AuthenticationProvider with a chain of responsibility.
-  DashboardAuthentication.forProviders({
-    required List<AuthenticationProvider> authProviders,
-  }) : _authenticationChain = authProviders;
-
-  /// Returns a version of this authentication with only the cron provider.
-  DashboardAuthentication get onlyCron {
-    final cronAuth = _authenticationChain.firstWhere(
-      (ac) => ac is DashboardCronAuthentication,
-    );
-    return DashboardAuthentication.forProviders(authProviders: [cronAuth]);
-  }
+  ChainOfAuthentication.forProviders(List<AuthenticationProvider> authProviders)
+    : _authenticationChain = authProviders;
 
   final List<AuthenticationProvider> _authenticationChain;
 
diff --git a/app_dart/lib/src/request_handling/presubmit_authentication.dart b/app_dart/lib/src/request_handling/presubmit_authentication.dart
index 863e4f7..2e28e1b 100644
--- a/app_dart/lib/src/request_handling/presubmit_authentication.dart
+++ b/app_dart/lib/src/request_handling/presubmit_authentication.dart
@@ -17,80 +17,6 @@
 import 'exceptions.dart';
 
 /// Class capable of authenticating [Request]s from the Checkrun page.
-///
-/// There are two types of authentication this class supports:
-///
-///  1. If the request has the `'X-Flutter-IdToken'` HTTP header
-///     set to a valid encrypted JWT token, then the request will be authenticated
-///     as a user account.
-///
-///     @google.com accounts can call APIs using curl and gcloud.
-///     E.g. curl '<api_url>' -H "X-Flutter-IdToken: $(gcloud auth print-identity-token)"
-///
-///     User accounts are only authorized if the user is either a "@google.com"
-///     account or is an [AllowedAccount] in Cocoon's Firestore.
-///
-/// 2. If the request has github.com token, then the request will be authenticated
-///    as a GitHub user account.
-///
-/// If none of the above authentication methods yield an authenticated
-/// request, then the request is unauthenticated, and any call to
-/// [authenticate] will throw an [Unauthenticated] exception.
-///
-/// See also:
-///
-///  * <https://cloud.google.com/appengine/docs/standard/python/reference/request-response-headers>
-@immutable
-interface class PresubmitAuthentication implements AuthenticationProvider {
-  PresubmitAuthentication({
-    required CacheService cache,
-    required Config config,
-    required FirebaseJwtValidator firebaseJwtValidator,
-    required FirestoreService firestore,
-    ClientContextProvider clientContextProvider = Providers.serviceScopeContext,
-    HttpClientProvider httpClientProvider = Providers.freshHttpClient,
-  }) {
-    _authenticationChain.addAll([
-      DashboardFirebaseAuthentication(
-        cache: cache,
-        validator: firebaseJwtValidator,
-        clientContextProvider: clientContextProvider,
-        firestore: firestore,
-      ),
-      GithubAuthentication(
-        cache: cache,
-        config: config,
-        validator: firebaseJwtValidator,
-        clientContextProvider: clientContextProvider,
-      ),
-    ]);
-  }
-
-  final _authenticationChain = <AuthenticationProvider>[];
-
-  /// Authenticates the specified [request] and returns the associated
-  /// [AuthenticatedContext].
-  ///
-  /// See the class documentation on [AuthenticationProvider] for a discussion
-  /// of the different types of authentication that are accepted.
-  ///
-  /// This will throw an [Unauthenticated] exception if the request is
-  /// unauthenticated.
-  @override
-  Future<AuthenticatedContext> authenticate(Request request) async {
-    /// Walk through the providers
-    for (final provider in _authenticationChain) {
-      try {
-        return await provider.authenticate(request);
-      } on Unauthenticated {
-        // nothing
-      }
-    }
-    throw const Unauthenticated('User is not signed in');
-  }
-}
-
-/// Class capable of authenticating [Request]s from the Checkrun page.
 class GithubAuthentication implements AuthenticationProvider {
   GithubAuthentication({
     required CacheService cache,
diff --git a/app_dart/tool/local_server.dart b/app_dart/tool/local_server.dart
index 9d0c0d3..41f12fd 100644
--- a/app_dart/tool/local_server.dart
+++ b/app_dart/tool/local_server.dart
@@ -36,17 +36,31 @@
   final bigQuery = await BigQueryService.from(const GoogleAuthProvider());
 
   final firebaseJwtValidator = FirebaseJwtValidator(cache: cache);
-  final dashboardAuthProvider = DashboardAuthentication(
+
+  const cronAuthentication = DashboardCronAuthentication();
+  final firebaseAuthentication = DashboardFirebaseAuthentication(
     cache: cache,
-    firebaseJwtValidator: firebaseJwtValidator,
+    validator: firebaseJwtValidator,
     firestore: firestore,
   );
-  final presubmitAuthProvider = PresubmitAuthentication(
+  final githubAuthentication = GithubAuthentication(
     cache: cache,
     config: config,
-    firebaseJwtValidator: firebaseJwtValidator,
-    firestore: firestore,
+    validator: firebaseJwtValidator,
   );
+
+  final cronAuthProvider = ChainOfAuthentication.forProviders([
+    cronAuthentication,
+  ]);
+  final dashboardAuthProvider = ChainOfAuthentication.forProviders([
+    cronAuthentication,
+    firebaseAuthentication,
+  ]);
+  final presubmitAuthProvider = ChainOfAuthentication.forProviders([
+    firebaseAuthentication,
+    githubAuthentication,
+  ]);
+
   final AuthenticationProvider swarmingAuthProvider =
       SwarmingAuthenticationProvider(config: config);
 
@@ -114,7 +128,7 @@
     bigQuery: bigQuery,
     cache: cache,
     dashboardAuthProvider: dashboardAuthProvider,
-    cronAuthProvider: dashboardAuthProvider.onlyCron,
+    cronAuthProvider: cronAuthProvider,
     presubmitAuthProvider: presubmitAuthProvider,
     branchService: branchService,
     buildBucketClient: buildBucketClient,
diff --git a/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart b/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart
index 506d7be..16bbdae 100644
--- a/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart
+++ b/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart
@@ -6,7 +6,7 @@
 import 'package:cocoon_service/src/request_handling/exceptions.dart';
 
 // ignore: must_be_immutable
-class FakeDashboardAuthentication implements DashboardAuthentication {
+class FakeDashboardAuthentication implements ChainOfAuthentication {
   FakeDashboardAuthentication({
     FakeClientContext? clientContext,
     this.authenticated = true,
@@ -23,9 +23,6 @@
       throw const Unauthenticated('Not authenticated');
     }
   }
-
-  @override
-  DashboardAuthentication get onlyCron => this;
 }
 
 // ignore: must_be_immutable