@@ -789,6 +789,122 @@ public async Task BearerTokenAuthenticationPolicy_BackgroundRefreshFailsAndLogs(
789789 Assert . IsTrue ( logged ) ;
790790 }
791791
792+ [ Test ]
793+ public async Task BearerTokenAuthenticationPolicy_SwitchedTenants ( )
794+ {
795+ var responses = new [ ]
796+ {
797+ new MockResponse ( 401 )
798+ . WithHeader ( "WWW-Authenticate" , @"Bearer authorization=""https://login.windows.net/de763a21-49f7-4b08-a8e1-52c8fbc103b4"", resource=""https://vault.azure.net""" ) ,
799+
800+ new MockResponse ( 200 ) ,
801+ new MockResponse ( 200 ) ,
802+
803+ // Moved tenants.
804+ new MockResponse ( 401 )
805+ . WithHeader ( "WWW-Authenticate" , @"Bearer authorization=""https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47"", resource=""https://vault.azure.net""" )
806+ . WithJson ( """
807+ {
808+ "error": {
809+ "code": "Unauthorized",
810+ "message": "AKV10032: Invalid issuer. Expected one of https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/, https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/, https://sts.windows.net/e2d54eb5-3869-4f70-8578-dee5fc7331f4/, https://sts.windows.net/33e01921-4d64-4f8c-a055-5bdaffd5e33d/, https://sts.windows.net/975f013f-7f24-47e8-a7d3-abc4752bf346/, found https://sts.windows.net/96be4b7a-defb-4dc2-a31f-49ee6145d5ab/."
811+ }
812+ }
813+ """ ) ,
814+
815+ new MockResponse ( 200 ) ,
816+ } ;
817+
818+ var transport = CreateMockTransport ( responses ) ;
819+
820+ string tenantId = null ;
821+ int callCount = 0 ;
822+ var credential = new TokenCredentialStub ( ( r , c ) =>
823+ {
824+ tenantId = r . TenantId ;
825+ Interlocked . Increment ( ref callCount ) ;
826+
827+ return new ( Guid . NewGuid ( ) . ToString ( ) , DateTimeOffset . Now . AddHours ( 2 ) ) ;
828+ } , IsAsync ) ;
829+ var policy = new ChallengeBasedAuthenticationTestPolicy ( credential , "scope" ) ;
830+
831+ await SendGetRequest ( transport , policy , uri : new ( "https://example.com/1/Original" ) ) ;
832+ Assert . AreEqual ( "de763a21-49f7-4b08-a8e1-52c8fbc103b4" , tenantId ) ;
833+ // This is initially 2 because the pipeline tries to pre-authenticate, then again when the test policy authenticates on a 401.
834+ Assert . AreEqual ( 2 , callCount ) ;
835+
836+ await SendGetRequest ( transport , policy , uri : new ( "https://example.com/1/Original" ) ) ;
837+ Assert . AreEqual ( "de763a21-49f7-4b08-a8e1-52c8fbc103b4" , tenantId ) ;
838+ Assert . AreEqual ( 2 , callCount ) ;
839+
840+ await SendGetRequest ( transport , policy , uri : new ( "https://example.com/1/Original" ) ) ;
841+ Assert . AreEqual ( "72f988bf-86f1-41af-91ab-2d7cd011db47" , tenantId ) ;
842+ // An additional call to TokenCredential.GetTokenAsync is expected now that the tenant has changed.
843+ Assert . AreEqual ( 3 , callCount ) ;
844+ }
845+
846+ private class ChallengeBasedAuthenticationTestPolicy : BearerTokenAuthenticationPolicy
847+ {
848+ public string TenantId { get ; private set ; }
849+
850+ private readonly ConcurrentQueue < string > _tenantIds = new (
851+ new [ ]
852+ {
853+ "de763a21-49f7-4b08-a8e1-52c8fbc103b4" ,
854+ "72f988bf-86f1-41af-91ab-2d7cd011db47" ,
855+ } ) ;
856+
857+ public ChallengeBasedAuthenticationTestPolicy ( TokenCredential credential , string scope ) : base ( credential , scope )
858+ {
859+ }
860+
861+ protected override void AuthorizeRequest ( HttpMessage message ) =>
862+ AuthorizeRequestAsync ( message , false ) . EnsureCompleted ( ) ;
863+
864+ protected override async ValueTask AuthorizeRequestAsync ( HttpMessage message ) =>
865+ await AuthorizeRequestAsync ( message , true ) . ConfigureAwait ( false ) ;
866+
867+ private async ValueTask AuthorizeRequestAsync ( HttpMessage message , bool isAsync )
868+ {
869+ if ( ! message . Request . Headers . Contains ( HttpHeader . Names . Authorization ) )
870+ {
871+ TokenRequestContext context = new ( new [ ] { "scope" } ) ;
872+ if ( isAsync )
873+ {
874+ await AuthenticateAndAuthorizeRequestAsync ( message , context ) ;
875+ }
876+ else
877+ {
878+ AuthenticateAndAuthorizeRequest ( message , context ) ;
879+ }
880+ }
881+ }
882+
883+ protected override bool AuthorizeRequestOnChallenge ( HttpMessage message ) =>
884+ AuthorizeRequestOnChallengeAsync ( message , false ) . EnsureCompleted ( ) ;
885+
886+ protected override async ValueTask < bool > AuthorizeRequestOnChallengeAsync ( HttpMessage message ) =>
887+ await AuthorizeRequestOnChallengeAsync ( message , true ) . ConfigureAwait ( false ) ;
888+
889+ private async ValueTask < bool > AuthorizeRequestOnChallengeAsync ( HttpMessage message , bool isAsync )
890+ {
891+ Assert . IsTrue ( _tenantIds . TryDequeue ( out string tenantId ) ) ;
892+ TenantId = tenantId ;
893+
894+ TokenRequestContext context = new ( new [ ] { "scope" } , tenantId : tenantId ) ;
895+ if ( isAsync )
896+ {
897+ await AuthenticateAndAuthorizeRequestAsync ( message , context ) ;
898+ }
899+ else
900+ {
901+ AuthenticateAndAuthorizeRequest ( message , context ) ;
902+ }
903+
904+ return true ;
905+ }
906+ }
907+
792908 private class TokenCredentialStub : TokenCredential
793909 {
794910 public TokenCredentialStub ( Func < TokenRequestContext , CancellationToken , AccessToken > handler , bool isAsync )
0 commit comments