@@ -54,6 +54,156 @@ public sealed class AutoSDKClientOptions
5454 Hooks . Add ( hook ?? throw new global ::System . ArgumentNullException ( nameof ( hook ) ) ) ;
5555 return this ;
5656 }
57+
58+ /// <summary>
59+ /// Optional per-request authorization provider invoked before each request is sent.
60+ /// Set this when the client is registered as a singleton in DI but each call needs
61+ /// a fresh credential resolved from a provider, secret-store, or session — instead
62+ /// of mutating the shared <c>Authorizations</c> list at construction time.
63+ /// </summary>
64+ public global ::Voicebox . IAutoSDKAuthorizationProvider ? AuthorizationProvider { get ; set ; }
65+
66+ /// <summary>
67+ /// Convenience helper that registers <see cref="AutoSDKAuthorizationProviderHook"/>
68+ /// using <paramref name="provider"/> so request-level auth is resolved without
69+ /// touching shared client state.
70+ /// </summary>
71+ /// <param name="provider"></param>
72+ public global ::Voicebox . AutoSDKClientOptions UseAuthorizationProvider (
73+ global ::Voicebox . IAutoSDKAuthorizationProvider provider )
74+ {
75+ AuthorizationProvider = provider ?? throw new global ::System . ArgumentNullException ( nameof ( provider ) ) ;
76+ if ( Hooks . Find ( static x => x is global ::Voicebox . AutoSDKAuthorizationProviderHook ) == null )
77+ {
78+ Hooks . Add ( new global ::Voicebox . AutoSDKAuthorizationProviderHook ( ) ) ;
79+ }
80+
81+ return this ;
82+ }
83+ }
84+
85+ /// <summary>
86+ /// A request-level authorization value supplied by <see cref="IAutoSDKAuthorizationProvider"/>.
87+ /// Mirrors the runtime fields the SDK applies for HTTP / OAuth2 / API-key auth without
88+ /// requiring the consumer to construct the generated <c>EndPointAuthorization</c> type.
89+ /// </summary>
90+ public readonly struct AutoSDKAuthorizationValue
91+ {
92+ /// <summary>
93+ /// Initializes a new <see cref="AutoSDKAuthorizationValue"/>.
94+ /// </summary>
95+ /// <param name="value"></param>
96+ /// <param name="scheme"></param>
97+ /// <param name="headerName"></param>
98+ /// <param name="location"></param>
99+ /// <param name="type"></param>
100+ public AutoSDKAuthorizationValue (
101+ string value ,
102+ string scheme = "Bearer" ,
103+ string ? headerName = null ,
104+ string location = "Header" ,
105+ string type = "Http" )
106+ {
107+ Value = value ?? string . Empty ;
108+ Scheme = string . IsNullOrWhiteSpace ( scheme ) ? "Bearer" : scheme ;
109+ HeaderName = headerName ?? string . Empty ;
110+ Location = string . IsNullOrWhiteSpace ( location ) ? "Header" : location ;
111+ Type = string . IsNullOrWhiteSpace ( type ) ? "Http" : type ;
112+ }
113+
114+ /// <summary>The credential value (token, API key, etc.).</summary>
115+ public string Value { get ; }
116+
117+ /// <summary>The HTTP authorization scheme — typically <c>Bearer</c>, <c>Basic</c>, or <c>Token</c>.</summary>
118+ public string Scheme { get ; }
119+
120+ /// <summary>The custom header name when <see cref="Type"/> is <c>ApiKey</c>; ignored for HTTP/OAuth2 auth.</summary>
121+ public string HeaderName { get ; }
122+
123+ /// <summary>The credential location — <c>Header</c>, <c>Query</c>, or <c>Cookie</c>.</summary>
124+ public string Location { get ; }
125+
126+ /// <summary>The auth type — <c>Http</c>, <c>OAuth2</c>, <c>OpenIdConnect</c>, or <c>ApiKey</c>.</summary>
127+ public string Type { get ; }
128+
129+ /// <summary>Convenience factory for a Bearer token.</summary>
130+ public static global ::Voicebox . AutoSDKAuthorizationValue Bearer ( string token ) => new ( value : token , scheme : "Bearer" ) ;
131+
132+ /// <summary>Convenience factory for an API-key header.</summary>
133+ public static global ::Voicebox . AutoSDKAuthorizationValue ApiKeyHeader ( string name , string value ) =>
134+ new ( value : value , headerName : name , location : "Header" , type : "ApiKey" ) ;
135+ }
136+
137+ /// <summary>
138+ /// Resolves request-level authorization values without mutating the shared client
139+ /// authorization list. Implementations should be safe to invoke concurrently —
140+ /// the hook calls them once per outgoing request.
141+ /// </summary>
142+ public interface IAutoSDKAuthorizationProvider
143+ {
144+ /// <summary>
145+ /// Returns one or more <see cref="AutoSDKAuthorizationValue"/> values to apply to
146+ /// the current request, or an empty list / <c>null</c> to leave the request as-is.
147+ /// </summary>
148+ /// <param name="context"></param>
149+ global ::System . Threading . Tasks . Task < global ::System . Collections . Generic . IReadOnlyList < global ::Voicebox . AutoSDKAuthorizationValue > ? > ResolveAsync (
150+ global ::Voicebox . AutoSDKHookContext context ) ;
151+ }
152+
153+ /// <summary>
154+ /// Built-in <see cref="IAutoSDKHook"/> that consults
155+ /// <see cref="AutoSDKClientOptions.AuthorizationProvider"/> before every outgoing
156+ /// request and stamps the resolved values onto the <see cref="global::System.Net.Http.HttpRequestMessage"/>.
157+ /// </summary>
158+ public sealed class AutoSDKAuthorizationProviderHook : global ::Voicebox . AutoSDKHook
159+ {
160+ /// <inheritdoc />
161+ public override async global ::System . Threading . Tasks . Task OnBeforeRequestAsync (
162+ global ::Voicebox . AutoSDKHookContext context )
163+ {
164+ context = context ?? throw new global ::System . ArgumentNullException ( nameof ( context ) ) ;
165+
166+ var provider = context . ClientOptions ? . AuthorizationProvider ;
167+ if ( provider == null || context . Request == null )
168+ {
169+ return ;
170+ }
171+
172+ var resolved = await provider . ResolveAsync ( context ) . ConfigureAwait ( false ) ;
173+ if ( resolved == null || resolved . Count == 0 )
174+ {
175+ return ;
176+ }
177+
178+ for ( var index = 0 ; index < resolved . Count ; index ++ )
179+ {
180+ ApplyAuthorization ( context . Request , resolved [ index ] ) ;
181+ }
182+ }
183+
184+ private static void ApplyAuthorization (
185+ global ::System . Net . Http . HttpRequestMessage request ,
186+ global ::Voicebox . AutoSDKAuthorizationValue authorization )
187+ {
188+ switch ( authorization . Type )
189+ {
190+ case "Http" :
191+ case "OAuth2" :
192+ case "OpenIdConnect" :
193+ request . Headers . Authorization = new global ::System . Net . Http . Headers . AuthenticationHeaderValue (
194+ scheme : authorization . Scheme ,
195+ parameter : authorization . Value ) ;
196+ break ;
197+ case "ApiKey" :
198+ if ( string . Equals ( authorization . Location , "Header" , global ::System . StringComparison . OrdinalIgnoreCase ) &&
199+ ! string . IsNullOrEmpty ( authorization . HeaderName ) )
200+ {
201+ request . Headers . Remove ( authorization . HeaderName ) ;
202+ request . Headers . TryAddWithoutValidation ( authorization . HeaderName , authorization . Value ?? string . Empty ) ;
203+ }
204+ break ;
205+ }
206+ }
57207 }
58208
59209 /// <summary>
0 commit comments