From 1ea91afe0a06e1f2b7a250d682236f853a41577a Mon Sep 17 00:00:00 2001 From: Joel Mut Date: Mon, 19 Jan 2026 12:13:48 +0000 Subject: [PATCH 1/3] Add AutoSignin feature + authorization deprecation in favor of userAuthorization property --- .../src/app/agentApplication.ts | 6 +- .../src/app/agentApplicationBuilder.ts | 19 +++- .../src/app/agentApplicationOptions.ts | 26 +++++- .../src/app/auth/authorizationManager.ts | 64 ++++++++++++-- packages/agents-hosting/src/app/auth/types.ts | 88 ++++++++++++++++++- .../test/hosting/app/authorization.test.ts | 2 +- 6 files changed, 187 insertions(+), 18 deletions(-) diff --git a/packages/agents-hosting/src/app/agentApplication.ts b/packages/agents-hosting/src/app/agentApplication.ts index ed8230389..7cd663fb7 100644 --- a/packages/agents-hosting/src/app/agentApplication.ts +++ b/packages/agents-hosting/src/app/agentApplication.ts @@ -125,7 +125,7 @@ export class AgentApplication { this._adapter = new CloudAdapter() } - if (this._options.authorization) { + if (this._options.userAuthorization || this._options.authorization) { this._authorizationManager = new AuthorizationManager(this, this._adapter.connectionManager) this._authorization = new UserAuthorization(this._authorizationManager) } @@ -433,7 +433,7 @@ export class AgentApplication { * */ public onSignInSuccess (handler: (context: TurnContext, state: TurnState, id?: string) => Promise): this { - if (this.options.authorization) { + if (this.options.userAuthorization || this.options.authorization) { this.authorization.onSignInSuccess(handler) } else { throw new Error( @@ -463,7 +463,7 @@ export class AgentApplication { * */ public onSignInFailure (handler: (context: TurnContext, state: TurnState, id?: string) => Promise): this { - if (this.options.authorization) { + if (this.options.userAuthorization || this.options.authorization) { this.authorization.onSignInFailure(handler) } else { throw new Error( diff --git a/packages/agents-hosting/src/app/agentApplicationBuilder.ts b/packages/agents-hosting/src/app/agentApplicationBuilder.ts index 24f9b0ab7..38457dee7 100644 --- a/packages/agents-hosting/src/app/agentApplicationBuilder.ts +++ b/packages/agents-hosting/src/app/agentApplicationBuilder.ts @@ -6,7 +6,7 @@ import { Storage } from '../storage' import { AgentApplication } from './agentApplication' import { AgentApplicationOptions } from './agentApplicationOptions' -import { AuthorizationOptions } from './auth/types' +import { AuthorizationOptions, UserAuthorizationOptions } from './auth/types' import { TurnState } from './turnState' /** @@ -55,12 +55,25 @@ export class AgentApplicationBuilder { // } /** + * Sets user authorization options for the AgentApplication. + * @param options The user authorization configuration + * @returns This builder instance for chaining + */ + public withAuthorization (options: UserAuthorizationOptions): this + /** + * @deprecated Use `withAuthorization(UserAuthorizationOptions)` instead. + * * Sets authentication options for the AgentApplication. * @param authHandlers The user identity authentication options * @returns This builder instance for chaining */ - public withAuthorization (authHandlers: AuthorizationOptions): this { - this._options.authorization = authHandlers + public withAuthorization (authHandlers: AuthorizationOptions): this + public withAuthorization (options: UserAuthorizationOptions | AuthorizationOptions): this { + if ('handlers' in options) { + this._options.userAuthorization = options as UserAuthorizationOptions + } else { + this._options.authorization = options + } return this } diff --git a/packages/agents-hosting/src/app/agentApplicationOptions.ts b/packages/agents-hosting/src/app/agentApplicationOptions.ts index 660cea692..b7731eee8 100644 --- a/packages/agents-hosting/src/app/agentApplicationOptions.ts +++ b/packages/agents-hosting/src/app/agentApplicationOptions.ts @@ -10,7 +10,7 @@ import { AdaptiveCardsOptions } from './adaptiveCards' import { InputFileDownloader } from './inputFileDownloader' import { TurnState } from './turnState' import { HeaderPropagationDefinition } from '../headerPropagation' -import { AuthorizationOptions } from './auth/types' +import { AuthorizationOptions, UserAuthorizationOptions } from './auth/types' import { Connections } from '../auth/connections' /** @@ -90,6 +90,8 @@ export interface AgentApplicationOptions { fileDownloaders?: InputFileDownloader[]; /** + * @deprecated Use `userAuthorization` instead. + * * Handlers for managing user authentication and authorization within the agent. * This includes OAuth flows, token management, and permission validation. * Use this to implement secure access to protected resources or user-specific data. @@ -98,6 +100,28 @@ export interface AgentApplicationOptions { */ authorization?: AuthorizationOptions; + /** + * Configuration for user authentication and authorization within the agent. + * This includes OAuth flows, token management, and permission validation. + * Use this to implement secure access to protected resources or user-specific data. + * + * @example + * ```typescript + * userAuthorization: { + * defaultHandlerName: 'graph', + * autoSignIn: true, + * handlers: { + * graph: { + * settings: { text: 'Sign in', title: 'Graph Sign In' } + * }, + * } + * } + * ``` + * + * @default undefined (no authorization required) + */ + userAuthorization?: UserAuthorizationOptions; + /** * Configuration options for handling Adaptive Card actions and interactions. * This controls how the agent processes card submissions, button clicks, and other diff --git a/packages/agents-hosting/src/app/auth/authorizationManager.ts b/packages/agents-hosting/src/app/auth/authorizationManager.ts index ff264017f..d8a379547 100644 --- a/packages/agents-hosting/src/app/auth/authorizationManager.ts +++ b/packages/agents-hosting/src/app/auth/authorizationManager.ts @@ -8,7 +8,7 @@ import { AgentApplication } from '../agentApplication' import { AgenticAuthorization, AzureBotAuthorization } from './handlers' import { TurnContext } from '../../turnContext' import { HandlerStorage } from './handlerStorage' -import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationOptions } from './types' +import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, UserAuthorizationOptions, AuthorizationHandlerOptions } from './types' import { Connections } from '../../auth/connections' const logger = debug('agents:authorization:manager') @@ -46,6 +46,7 @@ export type GetHandlerIds = (activity: Activity) => string[] | Promise */ export class AuthorizationManager { private _handlers: Record = {} + private _userAuthorizationOptions: UserAuthorizationOptions /** * Creates an instance of the AuthorizationManager. @@ -56,13 +57,15 @@ export class AuthorizationManager { throw new Error('Storage is required for Authorization. Ensure that a storage provider is configured in the AgentApplication options.') } - if (app.options.authorization === undefined || Object.keys(app.options.authorization).length === 0) { - throw new Error('The AgentApplication.authorization does not have any auth handlers') + this._userAuthorizationOptions = this.normalizeOptions() + + if (Object.keys(this._userAuthorizationOptions.handlers).length === 0) { + throw new Error('No authorization handlers configured. Provide handlers via \'AgentApplication.userAuthorization.handlers\' property.') } const settings: AuthorizationHandlerSettings = { storage: app.options.storage, connections } - for (const [id, handler] of Object.entries(app.options.authorization)) { - const options = this.loadOptions(id, handler) + for (const [id, handlerConfig] of Object.entries(this._userAuthorizationOptions.handlers ?? {})) { + const options = this.loadOptions(id, handlerConfig.settings) if (options.type === 'agentic') { this._handlers[id] = new AgenticAuthorization(id, options, settings) } else { @@ -71,18 +74,51 @@ export class AuthorizationManager { } } + /** + * Normalizes authorization options from either legacy or new format. + * Shows a deprecation warning if legacy format is used. + */ + private normalizeOptions (): UserAuthorizationOptions { + const { authorization, userAuthorization } = this.app.options + + // Prefer new format if provided + if (userAuthorization) { + return { + handlers: userAuthorization.handlers, + defaultHandlerName: userAuthorization.defaultHandlerName ?? process.env['AGENTAPPLICATION__USERAUTHORIZATION__DEFAULTHANDLERNAME'], + autoSignIn: userAuthorization.autoSignIn ?? (() => process.env['AGENTAPPLICATION__USERAUTHORIZATION__AUTOSIGNIN'] !== 'false'), + } + } + + // Fall back to legacy format with deprecation warning + if (authorization) { + logger.warn('The \'AgentApplication.authorization\' option is deprecated. Please use \'AgentApplication.userAuthorization\' with the new format instead.') + + // Convert legacy format to new format + const handlers: UserAuthorizationOptions['handlers'] = {} + for (const [id, options] of Object.entries(authorization)) { + handlers[id] = { settings: options } + } + + return { handlers } + } + + // No authorization configured + return { handlers: {} } + } + /** * Loads and validates the authorization handler options. */ - private loadOptions (id: string, options: AuthorizationOptions[string]) { - const result: AuthorizationOptions[string] = { + private loadOptions (id: string, options: AuthorizationHandlerOptions) { + const result: AuthorizationHandlerOptions = { ...options, type: (options.type ?? process.env[`${id}_type`])?.toLowerCase() as typeof options.type, } - // Validate supported types, agentic, and default (Azure Bot - undefined) + // Validate supported types: agentic, and default (Azure Bot - undefined) const supportedTypes = ['agentic', undefined] - if (!supportedTypes.includes(result.type)) { + if (result.type && !supportedTypes.includes(result.type.toLowerCase())) { throw new Error(`Unsupported authorization handler type: '${result.type}' for auth handler: '${id}'. Supported types are: '${supportedTypes.filter(Boolean).join('\', \'')}'.`) } @@ -110,6 +146,16 @@ export class AuthorizationManager { const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(context.activity) ?? []) ?? [] + // AutoSignIn feature: performs automatic sign-in using the provided default or first available handler. + if (this._userAuthorizationOptions.autoSignIn?.(context) && handlers.length === 0) { + const firstHandler = Object.values(this._handlers)[0] + const defaultHandler = this._handlers[this._userAuthorizationOptions.defaultHandlerName ?? ''] + if (!defaultHandler && this._userAuthorizationOptions.defaultHandlerName) { + logger.warn(`AutoSignIn is enabled but default handler '${this._userAuthorizationOptions.defaultHandlerName}' is not found. Falling back to the first available handler (${firstHandler?.id}).`) + } + handlers.push(defaultHandler ?? firstHandler) + } + for (const handler of handlers) { const status = await this.signin(storage, handler, context, active?.data) logger.debug(this.prefix(handler.id, `Sign-in status: ${status}`)) diff --git a/packages/agents-hosting/src/app/auth/types.ts b/packages/agents-hosting/src/app/auth/types.ts index d1dc17c08..13eeabe35 100644 --- a/packages/agents-hosting/src/app/auth/types.ts +++ b/packages/agents-hosting/src/app/auth/types.ts @@ -10,10 +10,96 @@ import { AgenticAuthorizationOptions, AzureBotAuthorizationOptions } from './han import { TokenResponse } from '../../oauth' import { Connections } from '../../auth/connections' +/** + * Handler options type for authorization handlers. + */ +export type AuthorizationHandlerOptions = AzureBotAuthorizationOptions | AgenticAuthorizationOptions + +/** + * Configuration for an individual authorization handler. + */ +export interface AuthorizationHandlerConfig { + /** + * The settings for the authorization handler. + */ + settings: AuthorizationHandlerOptions +} + +/** + * A record of authorization handler configurations keyed by their unique identifiers. + */ +export type AuthorizationHandlers = Record + +/** + * Function type for selecting whether to auto sign-in. + */ +export type AutoSignInSelector = (context: TurnContext) => Promise | boolean + +/** + * User authorization configuration options. + * @remarks + * Use this interface to configure authorization handlers with explicit settings. + * + * @example + * ```typescript + * userAuthorization: { + * defaultHandlerName: 'graph', + * autoSignIn: () => true, + * handlers: { + * graph: { + * settings: { text: 'Sign in with Microsoft Graph', title: 'Graph Sign In' } + * }, + * github: { + * settings: { text: 'Sign in with GitHub', title: 'GitHub Sign In' } + * }, + * } + * } + * ``` + */ +export interface UserAuthorizationOptions { + /** + * The name of the default authorization handler to use when no specific handler is specified. + * @remarks If not provided, the first handler in the list will be used as the default. + */ + defaultHandlerName?: string + /** + * Indicates whether to automatically sign in users when they interact with the application. + * @remarks Enabled by default if not specified. + */ + autoSignIn?: AutoSignInSelector + /** + * A record of authorization handlers keyed by their unique identifiers. + */ + handlers: AuthorizationHandlers +} + /** * Authorization configuration options. + * @deprecated Use {@link UserAuthorizationOptions} with the `userAuthorization` property instead. + * This flat structure will be removed in a future version. + * + * @example + * ```typescript + * // Deprecated: + * authorization: { + * graph: { text: '...', title: '...' }, + * github: { text: '...', title: '...' }, + * } + * + * // Use instead: + * userAuthorization: { + * handlers: { + * graph: { + * settings: { text: '...', title: '...' } + * }, + * github: { + * settings: { text: '...', title: '...' } + * }, + * } + * } + * ``` */ -export type AuthorizationOptions = Record +export type AuthorizationOptions = Record /** * Represents the status of a handler registration attempt. diff --git a/packages/agents-hosting/test/hosting/app/authorization.test.ts b/packages/agents-hosting/test/hosting/app/authorization.test.ts index dbad865b8..b0ded0425 100644 --- a/packages/agents-hosting/test/hosting/app/authorization.test.ts +++ b/packages/agents-hosting/test/hosting/app/authorization.test.ts @@ -27,7 +27,7 @@ describe('AgentApplication', () => { authorization: {} }) assert.equal(app.options.authorization, undefined) - }, { message: 'The AgentApplication.authorization does not have any auth handlers' }) + }, { message: 'No authorization handlers configured. Provide handlers via \'AgentApplication.userAuthorization.handlers\' property.' }) }) it('should initialize successfully with valid auth configuration', () => { From 2b737d61a6edbe23a18a5cea0893e4e8f332d88a Mon Sep 17 00:00:00 2001 From: Joel Mut Date: Mon, 19 Jan 2026 15:29:14 +0000 Subject: [PATCH 2/3] Apply suggestions --- .../src/app/agentApplicationOptions.ts | 2 +- .../src/app/auth/authorizationManager.ts | 5 +++-- packages/agents-hosting/src/app/auth/types.ts | 13 ++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/agents-hosting/src/app/agentApplicationOptions.ts b/packages/agents-hosting/src/app/agentApplicationOptions.ts index b7731eee8..7829993e1 100644 --- a/packages/agents-hosting/src/app/agentApplicationOptions.ts +++ b/packages/agents-hosting/src/app/agentApplicationOptions.ts @@ -109,7 +109,7 @@ export interface AgentApplicationOptions { * ```typescript * userAuthorization: { * defaultHandlerName: 'graph', - * autoSignIn: true, + * autoSignIn: () => true, * handlers: { * graph: { * settings: { text: 'Sign in', title: 'Graph Sign In' } diff --git a/packages/agents-hosting/src/app/auth/authorizationManager.ts b/packages/agents-hosting/src/app/auth/authorizationManager.ts index d8a379547..af1cd3892 100644 --- a/packages/agents-hosting/src/app/auth/authorizationManager.ts +++ b/packages/agents-hosting/src/app/auth/authorizationManager.ts @@ -118,7 +118,7 @@ export class AuthorizationManager { // Validate supported types: agentic, and default (Azure Bot - undefined) const supportedTypes = ['agentic', undefined] - if (result.type && !supportedTypes.includes(result.type.toLowerCase())) { + if (result.type && !supportedTypes.includes(result.type)) { throw new Error(`Unsupported authorization handler type: '${result.type}' for auth handler: '${id}'. Supported types are: '${supportedTypes.filter(Boolean).join('\', \'')}'.`) } @@ -147,7 +147,8 @@ export class AuthorizationManager { const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(context.activity) ?? []) ?? [] // AutoSignIn feature: performs automatic sign-in using the provided default or first available handler. - if (this._userAuthorizationOptions.autoSignIn?.(context) && handlers.length === 0) { + const shouldAutoSignIn = await this._userAuthorizationOptions.autoSignIn?.(context) + if (shouldAutoSignIn && handlers.length === 0) { const firstHandler = Object.values(this._handlers)[0] const defaultHandler = this._handlers[this._userAuthorizationOptions.defaultHandlerName ?? ''] if (!defaultHandler && this._userAuthorizationOptions.defaultHandlerName) { diff --git a/packages/agents-hosting/src/app/auth/types.ts b/packages/agents-hosting/src/app/auth/types.ts index 13eeabe35..8b4001bff 100644 --- a/packages/agents-hosting/src/app/auth/types.ts +++ b/packages/agents-hosting/src/app/auth/types.ts @@ -37,8 +37,19 @@ export type AutoSignInSelector = (context: TurnContext) => Promise | bo /** * User authorization configuration options. + * * @remarks - * Use this interface to configure authorization handlers with explicit settings. + * Properties can be configured via environment variables. + * Use the format: + * `AgentApplication__UserAuthorization__{propertyName}` + * + * @example + * ```env + * # For all handlers + * + * AGENTAPPLICATION__USERAUTHORIZATION__DEFAULTHANDLERNAME=graph + * AGENTAPPLICATION__USERAUTHORIZATION__AUTOSIGNIN=true + * ``` * * @example * ```typescript From edc552e34c07ca78fc4a09d1d6b89da3dff2831d Mon Sep 17 00:00:00 2001 From: Joel Mut Date: Tue, 20 Jan 2026 10:59:24 +0000 Subject: [PATCH 3/3] Apply copilot feedback --- .../agents-hosting/src/app/agentApplicationBuilder.ts | 8 ++++++-- packages/agents-hosting/src/app/auth/types.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/agents-hosting/src/app/agentApplicationBuilder.ts b/packages/agents-hosting/src/app/agentApplicationBuilder.ts index 38457dee7..dcaff8840 100644 --- a/packages/agents-hosting/src/app/agentApplicationBuilder.ts +++ b/packages/agents-hosting/src/app/agentApplicationBuilder.ts @@ -69,10 +69,14 @@ export class AgentApplicationBuilder { */ public withAuthorization (authHandlers: AuthorizationOptions): this public withAuthorization (options: UserAuthorizationOptions | AuthorizationOptions): this { - if ('handlers' in options) { + if ( + 'handlers' in options && + typeof options.handlers === 'object' && + Object.values(options.handlers).some(handler => typeof handler === 'object' && 'settings' in handler) + ) { this._options.userAuthorization = options as UserAuthorizationOptions } else { - this._options.authorization = options + this._options.authorization = options as AuthorizationOptions } return this } diff --git a/packages/agents-hosting/src/app/auth/types.ts b/packages/agents-hosting/src/app/auth/types.ts index 8b4001bff..673bab1a0 100644 --- a/packages/agents-hosting/src/app/auth/types.ts +++ b/packages/agents-hosting/src/app/auth/types.ts @@ -75,7 +75,7 @@ export interface UserAuthorizationOptions { defaultHandlerName?: string /** * Indicates whether to automatically sign in users when they interact with the application. - * @remarks Enabled by default if not specified. + * @remarks Auto sign-in is enabled by default and remains enabled unless explicitly disabled (for example, by setting the corresponding configuration or environment variable to 'false'). */ autoSignIn?: AutoSignInSelector /**