@@ -40,6 +40,8 @@ public class ContentstackClient : IContentstackClient
4040
4141 // OAuth token storage
4242 private readonly Dictionary < string , OAuthTokens > _oauthTokens = new Dictionary < string , OAuthTokens > ( ) ;
43+
44+ private bool _isRefreshingToken = false ;
4345 #endregion
4446
4547
@@ -228,20 +230,26 @@ internal ContentstackResponse InvokeSync<TRequest>(TRequest request, bool addAcc
228230 return ( ContentstackResponse ) ContentstackPipeline . InvokeSync ( context , addAcceptMediaHeader , apiVersion ) . httpResponse ;
229231 }
230232
231- internal Task < TResponse > InvokeAsync < TRequest , TResponse > ( TRequest request , bool addAcceptMediaHeader = false , string apiVersion = null )
233+ internal async Task < TResponse > InvokeAsync < TRequest , TResponse > ( TRequest request , bool addAcceptMediaHeader = false , string apiVersion = null )
232234 where TRequest : IContentstackService
233235 where TResponse : ContentstackResponse
234236 {
235237 ThrowIfDisposed ( ) ;
236238
239+ // Check and refresh OAuth tokens if needed before making API calls
240+ if ( contentstackOptions . IsOAuthToken && ! string . IsNullOrEmpty ( contentstackOptions . Authtoken ) )
241+ {
242+ await EnsureOAuthTokenIsValidAsync ( ) ;
243+ }
244+
237245 ExecutionContext context = new ExecutionContext (
238246 new RequestContext ( )
239247 {
240248 config = contentstackOptions ,
241249 service = request
242250 } ,
243251 new ResponseContext ( ) ) ;
244- return ContentstackPipeline . InvokeAsync < TResponse > ( context , addAcceptMediaHeader , apiVersion ) ;
252+ return await ContentstackPipeline . InvokeAsync < TResponse > ( context , addAcceptMediaHeader , apiVersion ) ;
245253 }
246254
247255 #region Dispose methods
@@ -502,6 +510,8 @@ internal void SetOAuthTokens(OAuthTokens tokens)
502510
503511 // Store the access token in the client options for use in HTTP requests
504512 // This will be used by the HTTP pipeline to inject the Bearer token
513+ // Note: We need both IsOAuthToken=true AND Authtoken=AccessToken because
514+ // the HTTP pipeline only has access to ContentstackClientOptions, not the full client
505515 contentstackOptions . Authtoken = tokens . AccessToken ;
506516 contentstackOptions . IsOAuthToken = true ;
507517 }
@@ -665,5 +675,74 @@ public Task<ContentstackResponse> GetUserAsync(ParameterCollection collection =
665675
666676 return InvokeAsync < GetLoggedInUserService , ContentstackResponse > ( getUser ) ;
667677 }
678+
679+ /// <summary>
680+ /// Ensures that the current OAuth token is valid and refreshes it if needed.
681+ /// This method is called before each API request to automatically handle token refresh.
682+ /// </summary>
683+ private async Task EnsureOAuthTokenIsValidAsync ( )
684+ {
685+ // Prevent recursive calls
686+ if ( _isRefreshingToken )
687+ {
688+ return ;
689+ }
690+
691+ try
692+ {
693+ // Find the OAuth tokens that match the current access token
694+ foreach ( var kvp in _oauthTokens )
695+ {
696+ var clientId = kvp . Key ;
697+ var tokens = kvp . Value ;
698+
699+ if ( tokens ? . AccessToken == contentstackOptions . Authtoken && tokens . NeedsRefresh )
700+ {
701+ // Set flag to prevent recursive calls
702+ _isRefreshingToken = true ;
703+
704+ try
705+ {
706+ // Get the OAuth handler for this client
707+ var oauthHandler = OAuth ( new Models . OAuthOptions
708+ {
709+ ClientId = clientId ,
710+ AppId = tokens . AppId
711+ } ) ;
712+
713+ // Refresh the tokens
714+ var refreshedTokens = await oauthHandler . RefreshTokenAsync ( tokens . RefreshToken ) ;
715+
716+ if ( refreshedTokens != null )
717+ {
718+ // Update the stored tokens
719+ StoreOAuthTokens ( clientId , refreshedTokens ) ;
720+
721+ // Update the client's current authentication
722+ contentstackOptions . Authtoken = refreshedTokens . AccessToken ;
723+ contentstackOptions . IsOAuthToken = true ; // Ensure OAuth flag is maintained
724+ }
725+ }
726+ catch ( Exception ex )
727+ {
728+ // Wrap any exception in OAuth exception with context
729+ throw new Exceptions . OAuthException (
730+ $ "OAuth token refresh failed for client '{ clientId } ': { ex . Message } ", ex ) ;
731+ }
732+ finally
733+ {
734+ _isRefreshingToken = false ;
735+ }
736+ }
737+ }
738+ }
739+ catch ( Exception ex )
740+ {
741+ // Wrap any exception in OAuth exception with context
742+ throw new Exceptions . OAuthException (
743+ $ "OAuth token validation failed: { ex . Message } ", ex ) ;
744+ }
745+ }
668746 }
669747}
748+
0 commit comments