mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-17 09:30:58 -07:00
feat(a2a): Add pluggable auth provider infrastructure
Introduces foundational architecture for pluggable authentication providers for A2A remote agents: - A2AAuthProvider interface extending SDK's AuthenticationHandler - BaseA2AAuthProvider abstract class with default retry logic - A2AAuthProviderFactory for creating providers (lazy-loaded) - Type definitions for all auth configs (google-credentials, apiKey, http, oauth2, openIdConnect) - Add auth field to RemoteAgentDefinition - validateAuthConfig for checking config against AgentCard requirements Files staged: - packages/core/src/agents/auth-provider/types.ts (new) - packages/core/src/agents/auth-provider/base-provider.ts (new) - packages/core/src/agents/auth-provider/factory.ts (new) - packages/core/src/agents/auth-provider/index.ts (new) - packages/core/src/agents/types.ts (modified - adds auth field)
This commit is contained in:
@@ -9,17 +9,33 @@ import type { A2AAuthProvider, A2AAuthProviderType } from './types.js';
|
||||
|
||||
/**
|
||||
* Abstract base class for A2A authentication providers.
|
||||
* Provides default implementations for optional methods.
|
||||
*/
|
||||
export abstract class BaseA2AAuthProvider implements A2AAuthProvider {
|
||||
/**
|
||||
* The type of authentication provider.
|
||||
*/
|
||||
abstract readonly type: A2AAuthProviderType;
|
||||
|
||||
/**
|
||||
* Get the HTTP headers to include in requests.
|
||||
* Subclasses must implement this method.
|
||||
*/
|
||||
abstract headers(): Promise<HttpHeaders>;
|
||||
|
||||
private static readonly MAX_AUTH_RETRIES = 2;
|
||||
private authRetryCount = 0;
|
||||
|
||||
/**
|
||||
* Default: retry on 401/403 with fresh headers.
|
||||
* Subclasses with cached tokens must override to force-refresh to avoid infinite retries.
|
||||
* Check if a request should be retried with new headers.
|
||||
*
|
||||
* The default implementation checks for 401/403 status codes and
|
||||
* returns fresh headers for retry. Subclasses can override for
|
||||
* custom retry logic.
|
||||
*
|
||||
* @param _req The original request init
|
||||
* @param res The response from the server
|
||||
* @returns New headers for retry, or undefined if no retry should be made
|
||||
*/
|
||||
async shouldRetryWithHeaders(
|
||||
_req: RequestInit,
|
||||
@@ -37,5 +53,17 @@ export abstract class BaseA2AAuthProvider implements A2AAuthProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {}
|
||||
/**
|
||||
* Initialize the provider. Override in subclasses that need async setup.
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
// Default: no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources. Override in subclasses that need cleanup.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
// Default: no-op
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,23 @@ import type {
|
||||
AuthValidationResult,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
* Options for creating an auth provider.
|
||||
*/
|
||||
export interface CreateAuthProviderOptions {
|
||||
/** Required for OAuth/OIDC token storage. */
|
||||
agentName?: string;
|
||||
/**
|
||||
* Name of the agent (for error messages and token storage).
|
||||
*/
|
||||
agentName: string;
|
||||
|
||||
/**
|
||||
* Auth configuration from the agent definition frontmatter.
|
||||
*/
|
||||
authConfig?: A2AAuthConfig;
|
||||
|
||||
/**
|
||||
* The fetched AgentCard with securitySchemes.
|
||||
*/
|
||||
agentCard?: AgentCard;
|
||||
}
|
||||
|
||||
@@ -23,33 +36,50 @@ export interface CreateAuthProviderOptions {
|
||||
* @see https://a2a-protocol.org/latest/specification/#451-securityscheme
|
||||
*/
|
||||
export class A2AAuthProviderFactory {
|
||||
/**
|
||||
* Create an auth provider from configuration.
|
||||
*
|
||||
* @param options Creation options including agent name and config
|
||||
* @returns The created auth provider, or undefined if no auth is needed
|
||||
*/
|
||||
static async create(
|
||||
options: CreateAuthProviderOptions,
|
||||
): Promise<A2AAuthProvider | undefined> {
|
||||
const { agentName: _agentName, authConfig, agentCard } = options;
|
||||
|
||||
// If no auth config, check if the AgentCard requires auth
|
||||
if (!authConfig) {
|
||||
if (
|
||||
agentCard?.securitySchemes &&
|
||||
Object.keys(agentCard.securitySchemes).length > 0
|
||||
) {
|
||||
return undefined; // Caller should prompt user to configure auth
|
||||
// AgentCard requires auth but none configured
|
||||
// The caller should handle this case by prompting the user
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create provider based on config type
|
||||
// Providers are lazy-loaded to support incremental implementation
|
||||
switch (authConfig.type) {
|
||||
case 'google-credentials':
|
||||
// TODO: Implement
|
||||
throw new Error('google-credentials auth provider not yet implemented');
|
||||
|
||||
case 'apiKey':
|
||||
// TODO: Implement
|
||||
throw new Error('apiKey auth provider not yet implemented');
|
||||
case 'apiKey': {
|
||||
const { ApiKeyAuthProvider } = await import('./api-key-provider.js');
|
||||
const provider = new ApiKeyAuthProvider(authConfig);
|
||||
await provider.initialize();
|
||||
return provider;
|
||||
}
|
||||
|
||||
case 'http':
|
||||
// TODO: Implement
|
||||
throw new Error('http auth provider not yet implemented');
|
||||
case 'http': {
|
||||
const { HttpAuthProvider } = await import('./http-auth-provider.js');
|
||||
const provider = new HttpAuthProvider(authConfig);
|
||||
await provider.initialize();
|
||||
return provider;
|
||||
}
|
||||
|
||||
case 'oauth2':
|
||||
// TODO: Implement
|
||||
@@ -60,6 +90,7 @@ export class A2AAuthProviderFactory {
|
||||
throw new Error('openIdConnect auth provider not yet implemented');
|
||||
|
||||
default: {
|
||||
// TypeScript exhaustiveness check
|
||||
const _exhaustive: never = authConfig;
|
||||
throw new Error(
|
||||
`Unknown auth type: ${(_exhaustive as A2AAuthConfig).type}`,
|
||||
@@ -68,33 +99,51 @@ export class A2AAuthProviderFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create provider directly from config, bypassing AgentCard validation. */
|
||||
/**
|
||||
* Create an auth provider directly from a config (for AgentCard fetching).
|
||||
* This bypasses AgentCard-based validation since we need auth to fetch the card.
|
||||
*
|
||||
* @param agentName Name of the agent
|
||||
* @param authConfig Auth configuration
|
||||
* @returns The created auth provider
|
||||
*/
|
||||
static async createFromConfig(
|
||||
agentName: string,
|
||||
authConfig: A2AAuthConfig,
|
||||
agentName?: string,
|
||||
): Promise<A2AAuthProvider> {
|
||||
const provider = await A2AAuthProviderFactory.create({
|
||||
authConfig,
|
||||
agentName,
|
||||
authConfig,
|
||||
});
|
||||
|
||||
// create() returns undefined only when authConfig is missing.
|
||||
// Since authConfig is required here, provider will always be defined
|
||||
// (or create() throws for unimplemented types).
|
||||
return provider!;
|
||||
if (!provider) {
|
||||
throw new Error(
|
||||
`Failed to create auth provider for config type: ${authConfig.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
/** Validate auth config against AgentCard's security requirements. */
|
||||
/**
|
||||
* Validate that the auth configuration satisfies the AgentCard's security requirements.
|
||||
*
|
||||
* @param authConfig The configured auth from agent-definition
|
||||
* @param securitySchemes The security schemes declared in the AgentCard
|
||||
* @returns Validation result with diff if invalid
|
||||
*/
|
||||
static validateAuthConfig(
|
||||
authConfig: A2AAuthConfig | undefined,
|
||||
securitySchemes: Record<string, SecurityScheme> | undefined,
|
||||
): AuthValidationResult {
|
||||
// If no security schemes required, any config is valid
|
||||
if (!securitySchemes || Object.keys(securitySchemes).length === 0) {
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
const requiredSchemes = Object.keys(securitySchemes);
|
||||
|
||||
// If auth is required but none configured
|
||||
if (!authConfig) {
|
||||
return {
|
||||
valid: false,
|
||||
@@ -106,6 +155,7 @@ export class A2AAuthProviderFactory {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if the configured type matches any of the required schemes
|
||||
const matchResult = A2AAuthProviderFactory.findMatchingScheme(
|
||||
authConfig,
|
||||
securitySchemes,
|
||||
@@ -145,6 +195,7 @@ export class A2AAuthProviderFactory {
|
||||
|
||||
case 'http':
|
||||
if (authConfig.type === 'http') {
|
||||
// Check if the scheme matches (Bearer, Basic, etc.)
|
||||
if (
|
||||
authConfig.scheme.toLowerCase() === scheme.scheme.toLowerCase()
|
||||
) {
|
||||
@@ -157,6 +208,7 @@ export class A2AAuthProviderFactory {
|
||||
authConfig.type === 'google-credentials' &&
|
||||
scheme.scheme.toLowerCase() === 'bearer'
|
||||
) {
|
||||
// Google credentials can provide Bearer tokens
|
||||
return { matched: true, missingConfig: [] };
|
||||
} else {
|
||||
missingConfig.push(
|
||||
@@ -178,6 +230,13 @@ export class A2AAuthProviderFactory {
|
||||
if (authConfig.type === 'openIdConnect') {
|
||||
return { matched: true, missingConfig: [] };
|
||||
}
|
||||
// Google credentials with target_audience can work as OIDC
|
||||
if (
|
||||
authConfig.type === 'google-credentials' &&
|
||||
authConfig.target_audience
|
||||
) {
|
||||
return { matched: true, missingConfig: [] };
|
||||
}
|
||||
missingConfig.push(
|
||||
`Scheme '${schemeName}' requires OpenID Connect authentication`,
|
||||
);
|
||||
@@ -201,7 +260,9 @@ export class A2AAuthProviderFactory {
|
||||
return { matched: false, missingConfig };
|
||||
}
|
||||
|
||||
/** Get human-readable description of required auth for error messages. */
|
||||
/**
|
||||
* Get a human-readable description of required auth for an AgentCard.
|
||||
*/
|
||||
static describeRequiredAuth(
|
||||
securitySchemes: Record<string, SecurityScheme>,
|
||||
): string {
|
||||
@@ -228,7 +289,6 @@ export class A2AAuthProviderFactory {
|
||||
break;
|
||||
default: {
|
||||
const _exhaustive: never = scheme;
|
||||
// This ensures TypeScript errors if a new SecurityScheme type is added
|
||||
descriptions.push(
|
||||
`Unknown (${name}): ${(_exhaustive as SecurityScheme).type}`,
|
||||
);
|
||||
|
||||
34
packages/core/src/agents/auth-provider/index.ts
Normal file
34
packages/core/src/agents/auth-provider/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Types
|
||||
export type {
|
||||
A2AAuthProvider,
|
||||
A2AAuthProviderType,
|
||||
A2AAuthConfig,
|
||||
GoogleCredentialsAuthConfig,
|
||||
ApiKeyAuthConfig,
|
||||
HttpAuthConfig,
|
||||
OAuth2AuthConfig,
|
||||
OpenIdConnectAuthConfig,
|
||||
BaseAuthConfig,
|
||||
AuthConfigDiff,
|
||||
AuthValidationResult,
|
||||
AuthenticationHandler,
|
||||
HttpHeaders,
|
||||
} from './types.js';
|
||||
|
||||
// Base class
|
||||
export { BaseA2AAuthProvider } from './base-provider.js';
|
||||
|
||||
// Factory
|
||||
export {
|
||||
A2AAuthProviderFactory,
|
||||
type CreateAuthProviderOptions,
|
||||
} from './factory.js';
|
||||
|
||||
// Note: Individual providers are lazy-loaded by the factory.
|
||||
// They will be exported as they are implemented in subsequent PRs.
|
||||
@@ -4,14 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { AuthenticationHandler, HttpHeaders } from '@a2a-js/sdk/client';
|
||||
|
||||
/**
|
||||
* Client-side auth configuration for A2A remote agents.
|
||||
* Corresponds to server-side SecurityScheme types from @a2a-js/sdk.
|
||||
* @see https://a2a-protocol.org/latest/specification/#451-securityscheme
|
||||
* Authentication provider types supported for A2A remote agents.
|
||||
* These align with the SecurityScheme types from the A2A specification.
|
||||
*/
|
||||
|
||||
import type { AuthenticationHandler } from '@a2a-js/sdk/client';
|
||||
|
||||
export type A2AAuthProviderType =
|
||||
| 'google-credentials'
|
||||
| 'apiKey'
|
||||
@@ -19,68 +17,213 @@ export type A2AAuthProviderType =
|
||||
| 'oauth2'
|
||||
| 'openIdConnect';
|
||||
|
||||
/**
|
||||
* Extended authentication handler interface for A2A remote agents.
|
||||
* Extends the base AuthenticationHandler from the A2A SDK with
|
||||
* lifecycle management methods.
|
||||
*/
|
||||
export interface A2AAuthProvider extends AuthenticationHandler {
|
||||
/**
|
||||
* The type of authentication provider.
|
||||
*/
|
||||
readonly type: A2AAuthProviderType;
|
||||
|
||||
/**
|
||||
* Initialize the provider. Called before first use.
|
||||
* For OAuth/OIDC, this may trigger discovery or browser-based auth.
|
||||
*/
|
||||
initialize?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Clean up any resources held by the provider.
|
||||
*/
|
||||
dispose?(): Promise<void>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Base configuration interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Base configuration shared by all auth types.
|
||||
*/
|
||||
export interface BaseAuthConfig {
|
||||
/**
|
||||
* If true, use this auth configuration to fetch the AgentCard.
|
||||
* Required when the AgentCard endpoint itself requires authentication.
|
||||
*/
|
||||
agent_card_requires_auth?: boolean;
|
||||
}
|
||||
|
||||
/** Client config for google-credentials (not in A2A spec, Gemini-specific). */
|
||||
// ============================================================================
|
||||
// Google Credentials configuration
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Configuration for Google Application Default Credentials (ADC).
|
||||
*/
|
||||
export interface GoogleCredentialsAuthConfig extends BaseAuthConfig {
|
||||
type: 'google-credentials';
|
||||
|
||||
/**
|
||||
* OAuth scopes to request. Required for access tokens.
|
||||
* @example ['https://www.googleapis.com/auth/cloud-platform']
|
||||
*/
|
||||
scopes?: string[];
|
||||
|
||||
/**
|
||||
* Target audience for ID token requests.
|
||||
* When specified, an ID token is requested instead of an access token.
|
||||
* Typically the URL of the Cloud Run service or other GCP resource.
|
||||
* @example 'https://my-agent.run.app'
|
||||
*/
|
||||
target_audience?: string;
|
||||
}
|
||||
|
||||
/** Client config corresponding to APIKeySecurityScheme. */
|
||||
// ============================================================================
|
||||
// API Key configuration
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Configuration for API Key authentication.
|
||||
* The API key can be sent in a header, query parameter, or cookie.
|
||||
*/
|
||||
export interface ApiKeyAuthConfig extends BaseAuthConfig {
|
||||
type: 'apiKey';
|
||||
/** The secret. Supports $ENV_VAR, !command, or literal. */
|
||||
|
||||
/**
|
||||
* The API key value. Supports:
|
||||
* - `$ENV_VAR`: Read from environment variable
|
||||
* - `!command`: Execute shell command and use output
|
||||
* - Literal string value
|
||||
*/
|
||||
key: string;
|
||||
/** Defaults to server's SecurityScheme.in value. */
|
||||
location?: 'header' | 'query' | 'cookie';
|
||||
/** Defaults to server's SecurityScheme.name value. */
|
||||
|
||||
/**
|
||||
* Where to include the API key in requests.
|
||||
* @default 'header'
|
||||
*/
|
||||
in?: 'header' | 'query' | 'cookie';
|
||||
|
||||
/**
|
||||
* The name of the header, query parameter, or cookie.
|
||||
* @default 'X-API-Key' for header, 'api_key' for query/cookie
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/** Client config corresponding to HTTPAuthSecurityScheme. */
|
||||
export type HttpAuthConfig = BaseAuthConfig & {
|
||||
type: 'http';
|
||||
} & (
|
||||
| {
|
||||
scheme: 'Bearer';
|
||||
/** For Bearer. Supports $ENV_VAR, !command, or literal. */
|
||||
token: string;
|
||||
}
|
||||
| {
|
||||
scheme: 'Basic';
|
||||
/** For Basic. Supports $ENV_VAR, !command, or literal. */
|
||||
username: string;
|
||||
/** For Basic. Supports $ENV_VAR, !command, or literal. */
|
||||
password: string;
|
||||
}
|
||||
);
|
||||
// ============================================================================
|
||||
// HTTP Auth configuration
|
||||
// ============================================================================
|
||||
|
||||
/** Client config corresponding to OAuth2SecurityScheme. */
|
||||
/**
|
||||
* Configuration for HTTP authentication (Bearer or Basic).
|
||||
*/
|
||||
export interface HttpAuthConfig extends BaseAuthConfig {
|
||||
type: 'http';
|
||||
|
||||
/**
|
||||
* The HTTP authentication scheme.
|
||||
*/
|
||||
scheme: 'Bearer' | 'Basic';
|
||||
|
||||
/**
|
||||
* The token for Bearer authentication. Supports:
|
||||
* - `$ENV_VAR`: Read from environment variable
|
||||
* - `!command`: Execute shell command and use output
|
||||
* - Literal string value
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Username for Basic authentication. Supports $ENV_VAR and !command.
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* Password for Basic authentication. Supports $ENV_VAR and !command.
|
||||
*/
|
||||
password?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OAuth 2.0 configuration
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Configuration for OAuth 2.0 authentication.
|
||||
* Endpoints can be discovered from the AgentCard's securitySchemes.
|
||||
*/
|
||||
export interface OAuth2AuthConfig extends BaseAuthConfig {
|
||||
type: 'oauth2';
|
||||
|
||||
/**
|
||||
* Client ID for OAuth. Supports $ENV_VAR and !command.
|
||||
*/
|
||||
client_id?: string;
|
||||
|
||||
/**
|
||||
* Client secret for OAuth. Supports $ENV_VAR and !command.
|
||||
* May be omitted for public clients using PKCE.
|
||||
*/
|
||||
client_secret?: string;
|
||||
|
||||
/**
|
||||
* OAuth scopes to request.
|
||||
*/
|
||||
scopes?: string[];
|
||||
}
|
||||
|
||||
/** Client config corresponding to OpenIdConnectSecurityScheme. */
|
||||
// ============================================================================
|
||||
// OpenID Connect configuration
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Configuration for OpenID Connect authentication.
|
||||
* This is a generic OIDC provider that works with any compliant issuer
|
||||
* (Auth0, Okta, Keycloak, Google, etc.).
|
||||
*/
|
||||
export interface OpenIdConnectAuthConfig extends BaseAuthConfig {
|
||||
type: 'openIdConnect';
|
||||
|
||||
/**
|
||||
* The OIDC issuer URL for discovery.
|
||||
* Used to fetch the .well-known/openid-configuration.
|
||||
* @example 'https://auth.example.com'
|
||||
*/
|
||||
issuer_url: string;
|
||||
|
||||
/**
|
||||
* Client ID for OIDC. Supports $ENV_VAR and !command.
|
||||
*/
|
||||
client_id: string;
|
||||
|
||||
/**
|
||||
* Client secret for OIDC. Supports $ENV_VAR and !command.
|
||||
* May be omitted for public clients.
|
||||
*/
|
||||
client_secret?: string;
|
||||
|
||||
/**
|
||||
* Target audience for ID token requests.
|
||||
* @example 'https://protected-agent.example.com'
|
||||
*/
|
||||
target_audience?: string;
|
||||
|
||||
/**
|
||||
* OAuth scopes to request.
|
||||
* @default ['openid']
|
||||
*/
|
||||
scopes?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Union type for all auth configs
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Union type of all supported A2A authentication configurations.
|
||||
*/
|
||||
export type A2AAuthConfig =
|
||||
| GoogleCredentialsAuthConfig
|
||||
| ApiKeyAuthConfig
|
||||
@@ -88,13 +231,47 @@ export type A2AAuthConfig =
|
||||
| OAuth2AuthConfig
|
||||
| OpenIdConnectAuthConfig;
|
||||
|
||||
// ============================================================================
|
||||
// Auth validation types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Describes a mismatch between configured auth and AgentCard requirements.
|
||||
*/
|
||||
export interface AuthConfigDiff {
|
||||
/**
|
||||
* Security scheme names required by the AgentCard.
|
||||
*/
|
||||
requiredSchemes: string[];
|
||||
|
||||
/**
|
||||
* The auth type configured in the agent definition, if any.
|
||||
*/
|
||||
configuredType?: A2AAuthProviderType;
|
||||
|
||||
/**
|
||||
* Description of what's missing to satisfy the requirements.
|
||||
*/
|
||||
missingConfig: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of validating auth configuration against AgentCard requirements.
|
||||
*/
|
||||
export interface AuthValidationResult {
|
||||
/**
|
||||
* Whether the configuration is valid for the AgentCard's requirements.
|
||||
*/
|
||||
valid: boolean;
|
||||
|
||||
/**
|
||||
* Details about the mismatch, if any.
|
||||
*/
|
||||
diff?: AuthConfigDiff;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Re-export useful types from the SDK
|
||||
// ============================================================================
|
||||
|
||||
export type { AuthenticationHandler, HttpHeaders };
|
||||
|
||||
@@ -109,6 +109,7 @@ export interface RemoteAgentDefinition<
|
||||
> extends BaseAgentDefinition<TOutput> {
|
||||
kind: 'remote';
|
||||
agentCardUrl: string;
|
||||
|
||||
/**
|
||||
* Optional authentication configuration for the remote agent.
|
||||
* If not specified, the agent will try to use defaults based on the AgentCard's
|
||||
|
||||
Reference in New Issue
Block a user