diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props
index 1825607ad303d..04f42ab83cd9d 100755
--- a/eng/Packages.Data.props
+++ b/eng/Packages.Data.props
@@ -42,6 +42,7 @@
+
diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
index 2cd4336a37a85..b4019fa5db076 100644
--- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
+++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
@@ -20,6 +20,8 @@
+
+
diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs
new file mode 100644
index 0000000000000..821f66c263d56
--- /dev/null
+++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs
@@ -0,0 +1,166 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Core.Pipeline;
+using Microsoft.Identity.Client;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Azure.Identity
+{
+ ///
+ /// A implementation which authenticates a user using the device code flow, and provides access tokens for that user account.
+ /// For more information on the device code authentication flow see https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Device-Code-Flow.
+ ///
+ public class DeviceCodeCredential : TokenCredential
+ {
+ private IPublicClientApplication _pubApp = null;
+ private HttpPipeline _pipeline = null;
+ private IAccount _account = null;
+ private IdentityClientOptions _options;
+ private string _clientId;
+ private Func _deviceCodeCallback;
+
+ ///
+ /// Protected constructor for mocking
+ ///
+ protected DeviceCodeCredential()
+ {
+
+ }
+
+ ///
+ /// Creates a new DeviceCodeCredential which will authenticate users with the specified application.
+ ///
+ /// The client id of the application to which the users will authenticate.
+ /// TODO: need to link to info on how the application has to be created to authenticate users, for multiple applications
+ /// The callback to be executed to display the device code to the user
+ public DeviceCodeCredential(string clientId, Func deviceCodeCallback)
+ : this(clientId, deviceCodeCallback, null)
+ {
+
+ }
+
+ ///
+ /// Creates a new DeviceCodeCredential with the specifeid options, which will authenticate users with the specified application.
+ ///
+ /// The client id of the application to which the users will authenticate
+ /// The client options for the newly created DeviceCodeCredential
+ /// The callback to be executed to display the device code to the user
+ public DeviceCodeCredential(string clientId, Func deviceCodeCallback, IdentityClientOptions options)
+ {
+ _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
+
+ _deviceCodeCallback = deviceCodeCallback ?? throw new ArgumentNullException(nameof(deviceCodeCallback));
+
+ _options = options ?? new IdentityClientOptions();
+
+ _pipeline = HttpPipelineBuilder.Build(_options, bufferResponse: true);
+
+ _pubApp = PublicClientApplicationBuilder.Create(_clientId).WithHttpClientFactory(new HttpPipelineClientFactory(_pipeline)).WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient").Build();
+ }
+
+ ///
+ /// Obtains a token for a user account, authenticating them through the device code authentication flow.
+ ///
+ /// The list of scopes for which the token will have access.
+ /// A controlling the request lifetime.
+ /// An which can be used to authenticate service client calls.
+ public override AccessToken GetToken(string[] scopes, CancellationToken cancellationToken = default)
+ {
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.Identity.DeviceCodeCredential.GetToken");
+
+ scope.Start();
+
+ try
+ {
+ if (_account != null)
+ {
+ try
+ {
+ AuthenticationResult result = _pubApp.AcquireTokenSilent(scopes, _account).ExecuteAsync(cancellationToken).GetAwaiter().GetResult();
+
+ return new AccessToken(result.AccessToken, result.ExpiresOn);
+ }
+ catch (MsalUiRequiredException)
+ {
+ // TODO: logging for exception here?
+ return GetTokenViaDeviceCodeAsync(scopes, cancellationToken).GetAwaiter().GetResult();
+ }
+ }
+ else
+ {
+ return GetTokenViaDeviceCodeAsync(scopes, cancellationToken).GetAwaiter().GetResult();
+ }
+ }
+ catch (Exception e)
+ {
+ scope.Failed(e);
+
+ throw;
+ }
+ }
+
+ ///
+ /// Obtains a token for a user account, authenticating them through the device code authentication flow.
+ ///
+ /// The list of scopes for which the token will have access.
+ /// A controlling the request lifetime.
+ /// An which can be used to authenticate service client calls.
+ public override async Task GetTokenAsync(string[] scopes, CancellationToken cancellationToken = default)
+ {
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.Identity.DeviceCodeCredential.GetToken");
+
+ scope.Start();
+
+ try
+ {
+ if (_account != null)
+ {
+ try
+ {
+ AuthenticationResult result = await _pubApp.AcquireTokenSilent(scopes, _account).ExecuteAsync(cancellationToken).ConfigureAwait(false);
+
+ return new AccessToken(result.AccessToken, result.ExpiresOn);
+ }
+ catch (MsalUiRequiredException)
+ {
+ // TODO: logging for exception here?
+ return await GetTokenViaDeviceCodeAsync(scopes, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ return await GetTokenViaDeviceCodeAsync(scopes, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (Exception e)
+ {
+ scope.Failed(e);
+
+ throw;
+ }
+ }
+
+ private async Task GetTokenViaDeviceCodeAsync(string[] scopes, CancellationToken cancellationToken)
+ {
+ AuthenticationResult result = await _pubApp.AcquireTokenWithDeviceCode(scopes, code => DeviceCodeCallback(code, cancellationToken)).ExecuteAsync(cancellationToken).ConfigureAwait(false);
+
+ _account = result.Account;
+
+ return new AccessToken(result.AccessToken, result.ExpiresOn);
+ }
+
+ private Task DeviceCodeCallback(DeviceCodeResult deviceCode, CancellationToken cancellationToken)
+ {
+ return _deviceCodeCallback(new DeviceCodeInfo(deviceCode), cancellationToken);
+ }
+
+
+ }
+}
diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeInfo.cs b/sdk/identity/Azure.Identity/src/DeviceCodeInfo.cs
new file mode 100644
index 0000000000000..4148d00b25c5f
--- /dev/null
+++ b/sdk/identity/Azure.Identity/src/DeviceCodeInfo.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Identity.Client;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Azure.Identity
+{
+ ///
+ /// Details of the device code to present to a user to allow them to authenticate through the device code authentication flow.
+ ///
+ public struct DeviceCodeInfo
+ {
+ internal DeviceCodeInfo(DeviceCodeResult deviceCode)
+ {
+ UserCode = deviceCode.UserCode;
+ DeviceCode = deviceCode.DeviceCode;
+ VerificationUrl = deviceCode.VerificationUrl;
+ ExpiresOn = deviceCode.ExpiresOn;
+ Interval = deviceCode.Interval;
+ Message = deviceCode.Message;
+ ClientId = deviceCode.ClientId;
+ Scopes = deviceCode.Scopes;
+ }
+
+ ///
+ /// User code returned by the service
+ ///
+ public string UserCode { get; private set; }
+
+ ///
+ /// Device code returned by the service
+ ///
+ public string DeviceCode { get; private set; }
+
+#pragma warning disable CA1056 // Uri properties should not be strings
+
+ ///
+ /// Verification URL where the user must navigate to authenticate using the device code and credentials.
+ ///
+ public string VerificationUrl { get; private set; }
+
+#pragma warning restore CA1056 // Uri properties should not be strings
+
+ ///
+ /// Time when the device code will expire.
+ ///
+ public DateTimeOffset ExpiresOn { get; private set; }
+
+ ///
+ /// Polling interval time to check for completion of authentication flow.
+ ///
+ public long Interval { get; private set; }
+
+ ///
+ /// User friendly text response that can be used for display purpose.
+ ///
+ public string Message { get; private set; }
+
+ ///
+ /// Identifier of the client requesting device code.
+ ///
+ public string ClientId { get; private set; }
+
+ ///
+ /// List of the scopes that would be held by token.
+ ///
+ public IReadOnlyCollection Scopes { get; private set; }
+ }
+}
diff --git a/sdk/identity/Azure.Identity/src/HttpExtensions.cs b/sdk/identity/Azure.Identity/src/HttpExtensions.cs
new file mode 100644
index 0000000000000..a43695c151035
--- /dev/null
+++ b/sdk/identity/Azure.Identity/src/HttpExtensions.cs
@@ -0,0 +1,81 @@
+using Azure.Core.Pipeline;
+using Azure;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Net;
+using Azure.Core.Http;
+
+namespace Azure.Identity
+{
+ internal static class HttpExtensions
+ {
+ public static async Task ToPipelineRequestAsync(this HttpRequestMessage request, HttpPipeline pipeline)
+ {
+ Request pipelineRequest = pipeline.CreateRequest();
+
+ pipelineRequest.Method = RequestMethod.Parse(request.Method.Method);
+
+ pipelineRequest.UriBuilder.Uri = request.RequestUri;
+
+ pipelineRequest.Content = await request.Content.ToPipelineRequestContentAsync().ConfigureAwait(false);
+
+ foreach (var header in request.Headers)
+ {
+ foreach (var value in header.Value)
+ {
+ pipelineRequest.Headers.Add(header.Key, value);
+ }
+ }
+
+ return pipelineRequest;
+ }
+
+ private static void AddHeader(HttpResponseMessage request, HttpHeader header)
+ {
+ if (request.Headers.TryAddWithoutValidation(header.Name, header.Value))
+ {
+ return;
+ }
+
+ if (!request.Content.Headers.TryAddWithoutValidation(header.Name, header.Value))
+ {
+ throw new InvalidOperationException("Unable to add header to request or content");
+ }
+ }
+
+ public static HttpResponseMessage ToHttpResponseMessage(this Response response)
+ {
+ HttpResponseMessage responseMessage = new HttpResponseMessage();
+
+ responseMessage.StatusCode = (HttpStatusCode)response.Status;
+
+ responseMessage.Content = new StreamContent(response.ContentStream);
+
+ foreach (var header in response.Headers)
+ {
+ if (!responseMessage.Headers.TryAddWithoutValidation(header.Name, header.Value))
+ {
+ if (!responseMessage.Content.Headers.TryAddWithoutValidation(header.Name, header.Value))
+ {
+ throw new InvalidOperationException("Unable to add header to request or content");
+ }
+ }
+ }
+
+ return responseMessage;
+ }
+
+ public static async Task ToPipelineRequestContentAsync(this HttpContent content)
+ {
+ if (content != null)
+ {
+ return HttpPipelineRequestContent.Create(await content.ReadAsStreamAsync().ConfigureAwait(false));
+ }
+
+ return null;
+ }
+
+ }
+}
diff --git a/sdk/identity/Azure.Identity/src/HttpPipelineClientFactory.cs b/sdk/identity/Azure.Identity/src/HttpPipelineClientFactory.cs
new file mode 100644
index 0000000000000..ecb2a7272424f
--- /dev/null
+++ b/sdk/identity/Azure.Identity/src/HttpPipelineClientFactory.cs
@@ -0,0 +1,53 @@
+using Azure.Core.Http;
+using Azure.Core.Pipeline;
+using Microsoft.Identity.Client;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Azure.Identity
+{
+ ///
+ /// This class is an HttpClient factory which creates an HttpClient which delegates it's transport to an HttpPipeline, to enable MSAL to send requests through an Azure.Core HttpPipeline.
+ ///
+ internal class HttpPipelineClientFactory : IMsalHttpClientFactory
+ {
+ private HttpPipeline _pipeline;
+
+ public HttpPipelineClientFactory(HttpPipeline pipeline)
+ {
+ _pipeline = pipeline;
+ }
+
+ public HttpClient GetHttpClient()
+ {
+ return new HttpClient(new PipelineHttpMessageHandler(_pipeline));
+ }
+
+ ///
+ /// An HttpMessageHandler which delegates SendAsync to a specified HttpPipeline.
+ ///
+ private class PipelineHttpMessageHandler : HttpMessageHandler
+ {
+ private HttpPipeline _pipeline;
+
+ public PipelineHttpMessageHandler(HttpPipeline pipeline)
+ {
+ _pipeline = pipeline;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ Request pipelineRequest = await request.ToPipelineRequestAsync(_pipeline).ConfigureAwait(false);
+
+ Response pipelineResponse = await _pipeline.SendRequestAsync(pipelineRequest, cancellationToken).ConfigureAwait(false);
+
+ return pipelineResponse.ToHttpResponseMessage();
+ }
+ }
+ }
+}
diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs
new file mode 100644
index 0000000000000..90426f9309324
--- /dev/null
+++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs
@@ -0,0 +1,381 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Core.Testing;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Azure.Identity.Tests
+{
+ public class DeviceCodeCredentialTests : ClientTestBase
+ {
+ public DeviceCodeCredentialTests(bool isAsync) : base(isAsync)
+ {
+ }
+
+ private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
+
+ private HashSet _requestedCodes = new HashSet();
+
+ private object _requestedCodesLock = new object();
+
+ private Task VerifyDeviceCode(DeviceCodeInfo code, string message)
+ {
+ Assert.AreEqual(message, code.Message);
+
+ return Task.CompletedTask;
+ }
+
+ private Task VerifyDeviceCodeAndCancel(DeviceCodeInfo code, string message, CancellationTokenSource cancelSource)
+ {
+ Assert.AreEqual(message, code.Message);
+
+ cancelSource.Cancel();
+
+ return Task.CompletedTask;
+ }
+
+ private async Task VerifyDeviceCodeCallbackCancellationToken(DeviceCodeInfo code, CancellationToken cancellationToken)
+ {
+ await Task.Delay(2000, cancellationToken);
+
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ private class MockException : Exception
+ {
+
+ }
+ private async Task ThrowingDeviceCodeCallback(DeviceCodeInfo code, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+
+ throw new MockException();
+ }
+
+ [Test]
+ public async Task AuthenticateWithDeviceCodeMockAsync()
+ {
+ var expectedCode = Guid.NewGuid().ToString();
+
+ var expectedToken = Guid.NewGuid().ToString();
+
+ var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken));
+
+ var options = new IdentityClientOptions() { Transport = mockTransport };
+
+ var cred = InstrumentClient(new DeviceCodeCredential(ClientId, (code, cancelToken) => VerifyDeviceCode(code, expectedCode), options));
+
+ AccessToken token = await cred.GetTokenAsync(new string[] { "https://vault.azure.net/.default" });
+
+ Assert.AreEqual(token.Token, expectedToken);
+ }
+
+ [Test]
+ public async Task AuthenticateWithDeviceCodeMockAsync2()
+ {
+ var expectedCode = Guid.NewGuid().ToString();
+
+ var expectedToken = Guid.NewGuid().ToString();
+
+ var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken));
+
+ var options = new IdentityClientOptions() { Transport = mockTransport };
+
+ var cred = InstrumentClient(new DeviceCodeCredential(ClientId, (code, cancelToken) => VerifyDeviceCode(code, expectedCode), options));
+
+ AccessToken token = await cred.GetTokenAsync(new string[] { "https://vault.azure.net/.default" });
+
+ Assert.AreEqual(token.Token, expectedToken);
+ }
+
+ [Test]
+ public void AuthenticateWithDeviceCodeMockVerifyMsalCancellationAsync()
+ {
+ var expectedCode = Guid.NewGuid().ToString();
+
+ var expectedToken = Guid.NewGuid().ToString();
+
+ var cancelSource = new CancellationTokenSource();
+
+ var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken));
+
+ var options = new IdentityClientOptions() { Transport = mockTransport };
+
+ var cred = InstrumentClient(new DeviceCodeCredential(ClientId, (code, cancelToken) => VerifyDeviceCodeAndCancel(code, expectedCode, cancelSource), options));
+
+ Assert.ThrowsAsync(async () => await cred.GetTokenAsync(new string[] { "https://vault.azure.net/.default" }, cancelSource.Token));
+ }
+
+ [Test]
+ public async Task AuthenticateWithDeviceCodeMockVerifyCallbackCancellationAsync()
+ {
+ var expectedCode = Guid.NewGuid().ToString();
+
+ var expectedToken = Guid.NewGuid().ToString();
+
+ var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken));
+
+ var options = new IdentityClientOptions() { Transport = mockTransport };
+
+ var cancelSource = new CancellationTokenSource(1000);
+
+ var cred = InstrumentClient(new DeviceCodeCredential(ClientId, VerifyDeviceCodeCallbackCancellationToken, options));
+
+ Task getTokenTask = cred.GetTokenAsync(new string[] { "https://vault.azure.net/.default" }, cancelSource.Token);
+
+ try
+ {
+ AccessToken token = await getTokenTask;
+
+ Assert.Fail();
+ }
+ catch(TaskCanceledException)
+ {
+
+ }
+ }
+
+ [Test]
+ public void AuthenticateWithDeviceCodeCallbackThrowsAsync()
+ {
+ var expectedCode = Guid.NewGuid().ToString();
+
+ var expectedToken = Guid.NewGuid().ToString();
+
+ var cancelSource = new CancellationTokenSource();
+
+ var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken));
+
+ var options = new IdentityClientOptions() { Transport = mockTransport };
+
+ var cred = InstrumentClient(new DeviceCodeCredential(ClientId, ThrowingDeviceCodeCallback, options));
+
+ Assert.ThrowsAsync(async () => await cred.GetTokenAsync(new string[] { "https://vault.azure.net/.default" }, cancelSource.Token));
+ }
+
+ private MockResponse ProcessMockRequest(MockRequest mockRequest, string code, string token)
+ {
+ string requestUrl = mockRequest.UriBuilder.Uri.AbsoluteUri;
+
+ if (requestUrl.StartsWith("https://login.microsoftonline.com/common/discovery/instance"))
+ {
+ return DiscoveryInstanceResponse;
+ }
+
+ if (requestUrl.StartsWith("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"))
+ {
+ return OpenIdConfigurationResponse;
+ }
+
+ if (requestUrl.StartsWith("https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode"))
+ {
+ return CreateDeviceCodeResponse(code);
+ }
+
+ if (requestUrl.StartsWith("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"))
+ {
+ return CreateTokenResponse(code, token);
+
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private MockResponse CreateTokenResponse(string code, string token)
+ {
+ lock(_requestedCodesLock)
+ {
+ if(_requestedCodes.Add(code))
+ {
+ return AuthorizationPendingResponse;
+ }
+ else
+ {
+ return CreateAuthorizationResponse(token);
+ }
+ }
+ }
+
+ private MockResponse CreateDeviceCodeResponse(string code)
+ {
+ var response = new MockResponse(200).WithContent($@"{{
+ ""user_code"": ""{code}"",
+ ""device_code"": ""{code}_{code}"",
+ ""verification_uri"": ""https://microsoft.com/devicelogin"",
+ ""expires_in"": 900,
+ ""interval"": 1,
+ ""message"": ""{code}""
+}}");
+
+ return response;
+ }
+
+ private MockResponse CreateAuthorizationResponse(string accessToken)
+ {
+ var response = new MockResponse(200).WithContent(@$"{{
+ ""token_type"": ""Bearer"",
+ ""scope"": ""https://vault.azure.net/user_impersonation https://vault.azure.net/.default"",
+ ""expires_in"": 3600,
+ ""ext_expires_in"": 3600,
+ ""access_token"": ""{accessToken}"",
+ ""refresh_token"": ""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9-eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ-SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"",
+ ""foci"": ""1"",
+ ""id_token"": ""eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InU0T2ZORlBId0VCb3NIanRyYXVPYlY4NExuWSJ9.eyJhdWQiOiJFMDFCNUY2NC03OEY1LTRGODgtQjI4Mi03QUUzOUI4QUM0QkQiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vRDEwOUI0NkUtM0E5Ri00NDQwLTg2MjItMjVEQjQxOTg1MDUxL3YyLjAiLCJpYXQiOjE1NjM5OTA0MDEsIm5iZiI6MTU2Mzk5MDQwMSwiZXhwIjoxNTYzOTk0MzAxLCJhaW8iOiJRMVV3TlV4YVNFeG9aak5uUWpSd00zcFRNamRrV2pSTFNVcEdMMVV3TWt0a2FrZDFTRkJVVlZwMmVFODRNMFZ0VXk4Mlp6TjJLM1JrVVVzeVQyVXhNamxJWTNKQ1p6MGlMQ0p1WVcxbElqb2lVMk52ZEhRZ1UyTiIsIm5hbWUiOiJTb21lIFVzZXIiLCJvaWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzb21ldXNlckBtaWNyb3NvZnQuY29tIiwic3ViIjoiQ0p6ZFdJaU9pSkdXakY0UVVOS1JFRnBRWFp6IiwidGlkIjoiMjRGRTMxMUYtN0E3MS00RjgzLTkxNkEtOTQ3OEQ0NUMwNDI3IiwidXRpIjoidFFqSTRNaTAzUVVVek9VSTRRVU0wUWtRaUxDSnBjM01pT2lKb2RIUiIsInZlciI6IjIuMCJ9.eVyG1AL8jwnTo3m9mGsV4EDHa_8PN6rRPEN9E3cQzxNoPU9HZTFt1SgOnLB7n1a4J_E3iVoZ3VB5I-NdDBESRdlg1k4XlrWqtisxl3I7pvWVFZKEhwHYYQ_nZITNeCb48LfZNz-Mr4EZeX6oyUymha5tOomikBLLxP78LOTlbGQiFn9AjtV0LtMeoiDf-K9t-kgU-XwsVjCyFKFBQhcyv7zaBEpeA-Kzh3-HG7wZ-geteM5y-JF97nD_rJ8ow1FmvtDYy6MVcwuNTv2YYT8dn8s-SGB4vpNNignlL0QgYh2P2cIrPdhZVc2iQqYTn_FK_UFPqyb_MZSjl1QkXVhgJA"",
+ ""client_info"": ""eyJ1aWQiOiIyQ0M5QzNBOC0yNTA5LTQyMEYtQjAwQi02RTczQkM1MURDQjUiLCJ1dGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3In0""
+}}");
+ return response;
+ }
+
+ private static MockResponse DiscoveryInstanceResponse
+ {
+ get
+ {
+ return new MockResponse(200).WithContent(@"
+{
+ ""tenant_discovery_endpoint"": ""https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"",
+ ""api-version"": ""1.1"",
+ ""metadata"": [
+ {
+ ""preferred_network"": ""login.microsoftonline.com"",
+ ""preferred_cache"": ""login.windows.net"",
+ ""aliases"": [
+ ""login.microsoftonline.com"",
+ ""login.windows.net"",
+ ""login.microsoft.com"",
+ ""sts.windows.net""
+ ]
+},
+ {
+ ""preferred_network"": ""login.partner.microsoftonline.cn"",
+ ""preferred_cache"": ""login.partner.microsoftonline.cn"",
+ ""aliases"": [
+ ""login.partner.microsoftonline.cn"",
+ ""login.chinacloudapi.cn""
+ ]
+ },
+ {
+ ""preferred_network"": ""login.microsoftonline.de"",
+ ""preferred_cache"": ""login.microsoftonline.de"",
+ ""aliases"": [
+ ""login.microsoftonline.de""
+ ]
+ },
+ {
+ ""preferred_network"": ""login.microsoftonline.us"",
+ ""preferred_cache"": ""login.microsoftonline.us"",
+ ""aliases"": [
+ ""login.microsoftonline.us"",
+ ""login.usgovcloudapi.net""
+ ]
+ },
+ {
+ ""preferred_network"": ""login-us.microsoftonline.com"",
+ ""preferred_cache"": ""login-us.microsoftonline.com"",
+ ""aliases"": [
+ ""login-us.microsoftonline.com""
+ ]
+ }
+ ]
+}");
+ }
+ }
+
+ private static MockResponse OpenIdConfigurationResponse
+ {
+ get
+ {
+ return new MockResponse(200).WithContent(@"{
+ ""authorization_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/authorize"",
+ ""token_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/token"",
+ ""token_endpoint_auth_methods_supported"": [
+ ""client_secret_post"",
+ ""private_key_jwt"",
+ ""client_secret_basic""
+ ],
+ ""jwks_uri"": ""https://login.microsoftonline.com/common/discovery/v2.0/keys"",
+ ""response_modes_supported"": [
+ ""query"",
+ ""fragment"",
+ ""form_post""
+ ],
+ ""subject_types_supported"": [
+ ""pairwise""
+ ],
+ ""id_token_signing_alg_values_supported"": [
+ ""RS256""
+ ],
+ ""http_logout_supported"": true,
+ ""frontchannel_logout_supported"": true,
+ ""end_session_endpoint"": ""https://login.microsoftonline.com/common/oauth2/v2.0/logout"",
+ ""response_types_supported"": [
+ ""code"",
+ ""id_token"",
+ ""code id_token"",
+ ""id_token token""
+ ],
+ ""scopes_supported"": [
+ ""openid"",
+ ""profile"",
+ ""email"",
+ ""offline_access""
+ ],
+ ""issuer"": ""https://login.microsoftonline.com/{tenantid}/v2.0"",
+ ""claims_supported"": [
+ ""sub"",
+ ""iss"",
+ ""cloud_instance_name"",
+ ""cloud_instance_host_name"",
+ ""cloud_graph_host_name"",
+ ""msgraph_host"",
+ ""aud"",
+ ""exp"",
+ ""iat"",
+ ""auth_time"",
+ ""acr"",
+ ""nonce"",
+ ""preferred_username"",
+ ""name"",
+ ""tid"",
+ ""ver"",
+ ""at_hash"",
+ ""c_hash"",
+ ""email""
+ ],
+ ""request_uri_parameter_supported"": false,
+ ""userinfo_endpoint"": ""https://graph.microsoft.com/oidc/userinfo"",
+ ""tenant_region_scope"": null,
+ ""cloud_instance_name"": ""microsoftonline.com"",
+ ""cloud_graph_host_name"": ""graph.windows.net"",
+ ""msgraph_host"": ""graph.microsoft.com"",
+ ""rbac_url"": ""https://pas.windows.net""
+}");
+ }
+ }
+
+ private static MockResponse AuthorizationPendingResponse
+ {
+ get
+ {
+ return new MockResponse(404).WithContent(@"{
+ ""error"": ""authorization_pending"",
+ ""error_description"": ""AADSTS70016: Pending end-user authorization.\r\nTrace ID: c40ce91e-5009-4e64-9a10-7732b2500100\r\nCorrelation ID: 73a2edae-f747-44da-8ebf-7cba565fe49d\r\nTimestamp: 2019-07-24 17:49:13Z"",
+ ""error_codes"": [
+ 70016
+ ],
+ ""timestamp"": ""2019-07-24 17:49:13Z"",
+ ""trace_id"": ""c40ce91e-5009-4e64-9a10-7732b2500100"",
+ ""correlation_id"": ""73a2edae-f747-44da-8ebf-7cba565fe49d""
+}");
+ }
+ }
+ }
+
+
+
+}
diff --git a/sdk/identity/Azure.Identity/tests/MockExtensions.cs b/sdk/identity/Azure.Identity/tests/MockExtensions.cs
new file mode 100644
index 0000000000000..04c3ed920a3c2
--- /dev/null
+++ b/sdk/identity/Azure.Identity/tests/MockExtensions.cs
@@ -0,0 +1,17 @@
+using Azure.Core.Testing;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Azure.Identity.Tests
+{
+ internal static class MockExtensions
+ {
+ public static MockResponse WithContent(this MockResponse response, string content)
+ {
+ response.SetContent(content);
+
+ return response;
+ }
+ }
+}