diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index c54656371a62..996201d289e5 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -4,6 +4,7 @@ ### New Features - Restoring Application Authentication APIs from 1.2.0-preview.6 - Added `IncludeX5CClaimHeader` to `ClientCertificateCredentialOptions` to enable subject name / issuer authentication with the `ClientCertificateCredential`. +- Added `RedirectUri` to `InteractiveBrowserCredentialOptions` to enable authentication with user specified application with a custom redirect url. ### Fixes and improvements - Fixed issue with non GUID Client Ids (Issue [#14585](https://github.com/Azure/azure-sdk-for-net/issues/14585)) diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index a1696148f5fd..efb4966ab56f 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -178,6 +178,7 @@ public InteractiveBrowserCredentialOptions() { } public string ClientId { get { throw null; } set { } } public bool DisableAutomaticAuthentication { get { throw null; } set { } } public bool EnablePersistentCache { get { throw null; } set { } } + public System.Uri RedirectUri { get { throw null; } set { } } public string TenantId { get { throw null; } set { } } } public partial class ManagedIdentityCredential : Azure.Core.TokenCredential diff --git a/sdk/identity/Azure.Identity/src/Constants.cs b/sdk/identity/Azure.Identity/src/Constants.cs index 88ae55df539c..d1239a09afb3 100644 --- a/sdk/identity/Azure.Identity/src/Constants.cs +++ b/sdk/identity/Azure.Identity/src/Constants.cs @@ -20,6 +20,8 @@ internal class Constants public static readonly TimeSpan SharedTokenCacheAccessRetryDelay = TimeSpan.FromMilliseconds(600); + public const string DefaultRedirectUrl = "http://localhost"; + public static readonly string DefaultMsalTokenCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".IdentityService"); public const string DefaultMsalTokenCacheKeychainService = "Microsoft.Developer.IdentityService"; diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs index 96fa97d49d94..ccb5b6b5efc8 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredential.cs @@ -78,7 +78,9 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre Pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - Client = client ?? new MsalPublicClient(Pipeline, tenantId, clientId, "http://localhost", options as ITokenCacheOptions); + var redirectUrl = (options as InteractiveBrowserCredentialOptions)?.RedirectUri?.AbsoluteUri ?? Constants.DefaultRedirectUrl; + + Client = client ?? new MsalPublicClient(Pipeline, tenantId, clientId, redirectUrl, options as ITokenCacheOptions); } /// diff --git a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredentialOptions.cs b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredentialOptions.cs index cb79ea1f5e0c..d972f70d2ed8 100644 --- a/sdk/identity/Azure.Identity/src/InteractiveBrowserCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/InteractiveBrowserCredentialOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Threading; namespace Azure.Identity @@ -36,6 +37,12 @@ public class InteractiveBrowserCredentialOptions : TokenCredentialOptions, IToke /// public bool AllowUnencryptedCache { get; set; } + /// + /// Uri where the STS will call back the application with the security token. This parameter is not required if the caller is not using a custom . In + /// the case that the caller is using their own the value must match the redirect url specified when creating the application registration. + /// + public Uri RedirectUri { get; set; } + /// /// The captured from a previous authentication. /// diff --git a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs index b1b628259d67..8841f476ed92 100644 --- a/sdk/identity/Azure.Identity/src/MsalPublicClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalPublicClient.cs @@ -13,7 +13,7 @@ namespace Azure.Identity { internal class MsalPublicClient : MsalClientBase { - private readonly string _redirectUrl; + internal string RedirectUrl { get; } protected MsalPublicClient() { @@ -22,7 +22,7 @@ protected MsalPublicClient() public MsalPublicClient(CredentialPipeline pipeline, string tenantId, string clientId, string redirectUrl, ITokenCacheOptions cacheOptions) : base(pipeline, tenantId, clientId, cacheOptions) { - _redirectUrl = redirectUrl; + RedirectUrl = redirectUrl; } protected override ValueTask CreateClientAsync(bool async, CancellationToken cancellationToken) @@ -33,9 +33,9 @@ protected override ValueTask CreateClientAsync(bool as PublicClientApplicationBuilder pubAppBuilder = PublicClientApplicationBuilder.Create(ClientId).WithAuthority(authorityUri).WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline)); - if (!string.IsNullOrEmpty(_redirectUrl)) + if (!string.IsNullOrEmpty(RedirectUrl)) { - pubAppBuilder = pubAppBuilder.WithRedirectUri(_redirectUrl); + pubAppBuilder = pubAppBuilder.WithRedirectUri(RedirectUrl); } return new ValueTask(pubAppBuilder.Build()); diff --git a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialCtorTests.cs b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialCtorTests.cs index 63979f6eacb8..62da55872a0f 100644 --- a/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialCtorTests.cs +++ b/sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialCtorTests.cs @@ -38,7 +38,8 @@ public void ValidateConstructorOverload1() DisableAutomaticAuthentication = true, EnablePersistentCache = true, AllowUnencryptedCache = true, - AuthenticationRecord = new AuthenticationRecord() + AuthenticationRecord = new AuthenticationRecord(), + RedirectUri = new Uri("https://localhost:8080"), }; credential = new InteractiveBrowserCredential(options); @@ -118,6 +119,15 @@ public void AssertOptionsHonored(InteractiveBrowserCredentialOptions options, In Assert.AreEqual(options.EnablePersistentCache, credential.Client.EnablePersistentCache); Assert.AreEqual(options.AllowUnencryptedCache, credential.Client.AllowUnencryptedCache); Assert.AreEqual(options.AuthenticationRecord, credential.Record); + + if (options.RedirectUri != null) + { + Assert.AreEqual(options.RedirectUri, new Uri(credential.Client.RedirectUrl)); + } + else + { + Assert.AreEqual(Constants.DefaultRedirectUrl, credential.Client.RedirectUrl); + } } }