-
Notifications
You must be signed in to change notification settings - Fork 4.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[QUERY] Azure.Identity 1.11.4: how to use WebView2 control? #44550
Comments
Thank you for your feedback. Tagging and routing to the team member best able to assist. |
Hi @WolfgangHG Would you mind trying to use our Broker package, which uses the Web Account Manager? I think it will work with the embedded browser option. After adding the reference, you would change your options code to this: // get the window handle of the form
IntPtr hwnd = this.Handle;
InteractiveBrowserCredentialBrokerOptions options = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle: hwnd)
{
TenantId = this.textBoxTenant.Text,
ClientId = this.textBoxClientID.Text,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("http://localhost"),
BrowserCustomization = customizationOptions
}; |
Hi @WolfgangHG. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
@christothes I got it working after having added a new redirect URI in Azure (specifying "http://localhost" in InteractiveBrowserCredentialBrokerOptions does not seem to have an effect) :
Just for the records: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity.Broker/README.md#redirect-uris This is bad, as we would have to tell all of our customers to add a new redirect uri to their azure configuration - and the url is specific for each customer. The "BrowserCustomizationOptions.UseEmbeddedWebView" option does not seem to have any effect, it is always a login dialog. But it does not show the list of previously used logins (and probably does not allow selecting an already logged in user). This worked with Azure.Identity 1.11.3. |
Glad you got it working.
Yes, you are correct. In the case of the broker based login, the window is owned by the operating system. The window handle is only used to ensure it appears above the window related to the handle. |
Hi @WolfgangHG. Thank you for opening this issue and giving us the opportunity to assist. We believe that this has been addressed. If you feel that further discussion is needed, please add a comment with the text "/unresolve" to remove the "issue-addressed" label and continue the conversation. |
@christothes Do you internally use a "PublicClientApplicationBuilder"? If yes: is there any chance to use the "PublicClientApplicationBuilder.WithWindowsEmbeddedBrowserSupport" extension method approach suggested by Microsoft.Identity.Client? I hope that this one causes less trouble than the Broker approach (change to the redirect url required, clunky modality)? You set the status to "issue-addressed", but currently we have only found a workaround that I don't like ;-). |
/unresolve |
We don't intend to expose the functionality for |
Hi @WolfgangHG. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
@christothes I took a look at the code. It seems I cannot extend It might work if the interface Attached is a diff of the necessary changes to Azure.Identity. What do you think? With this code, I could create my own options subclass:
But I assume the interface "IMsalPublicClientInitializerOptions" is more of an internal hack for some testing purposes, so I am not sure whether this is the proper place to extend Azure.Identity. I would prefer to move "BeforeBuildClient" to "InteractiveBrowserCredentialOptions", so that "IMsalPublicClientInitializerOptions" could be kept internal. |
Another option could be to subclass |
Hi @WolfgangHG - |
@christothes This is unfortunately not possible I fear. I start from the Microsoft Graph .NET Client Library, and the https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/src/Microsoft.Graph/GraphServiceClient.cs has only constructors that accept TokenCredential or IAuthenticationProvider. Do you have a suggestion or a sample how to create a winforms login without Azure.Identity? I actually don't want to copy a lot of lines of code to call What about my suggestion to move the "BeforeBuildClient" property to "InteractiveBrowserCredentialOptions", so that "IMsalPublicClientInitializerOptions" could be kept internal? This does not expose MSAL classes I think. |
One option would be to create your own implementation of |
Well, I got it to work, but I don't like the solution: string[] scopes = new string[] { "Calendars.Read" };
var app = PublicClientApplicationBuilder.Create(this.textBoxClientID.Text)
.WithDefaultRedirectUri()
.WithTenantId(this.textBoxTenant.Text)
.WithWindowsEmbeddedBrowserSupport()
.Build();
AuthenticationResult result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
MyTokenCredential mtc = new MyTokenCredential(result);
GraphServiceClient graphClient = new GraphServiceClient(mtc);
Calendar cal = await graphClient.Me.Calendar.GetAsync();
MessageBox.Show(this, "Calendar ID: " + cal.Id); Class "MyTokenCredential" is simple: public class MyTokenCredential : TokenCredential
{
AuthenticationResult result;
public MyTokenCredential(AuthenticationResult result)
{
this.result = result;
}
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
AccessToken token = new AccessToken(this.result.AccessToken, this.result.ExpiresOn);
return new ValueTask<AccessToken>(token);
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
} Could you comment on whether this is reasonable? Fortunately, our app does not need features like token refresh, as we use the interactive login only for short term operations and get a fresh token before each operation (at least I hope our usage works like this). But with this code, we probably loose all the features of Azure.Identity. |
Your I'd also recommend you acquire the token similar to how the example linked in my previous response does it so that you only get prompted for credentials the first time |
You mean something like this? In the form button click: private MyTokenCredential mtc;
private async void buttonDoSomething_Click(object sender, EventArgs e)
{
if (this.mtc == null)
{
mtc = new MyTokenCredential(this.textBoxTenant.Text, this.textBoxClientID.Text);
}
GraphServiceClient graphClient = new GraphServiceClient(mtc);
Calendar cal = await graphClient.Me.Calendar.GetAsync();
MessageBox.Show(this, "Calendar ID: " + cal.Id);
} Class "MyTokenCredential" looks like this: public class MyTokenCredential : TokenCredential
{
private IPublicClientApplication clientApp;
public MyTokenCredential(string _tenant, string _clientId)
{
this.clientApp = PublicClientApplicationBuilder.Create(_clientId)
.WithDefaultRedirectUri()
.WithTenantId(_tenant)
.WithWindowsEmbeddedBrowserSupport()
.Build();
}
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
string[] scopes = new string[] { "Calendars.Read" };
AuthenticationResult result;
try
{
var accounts = await this.clientApp.GetAccountsAsync();
result = await this.clientApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
result = await this.clientApp.AcquireTokenInteractive(scopes).ExecuteAsync();
}
AccessToken token = new AccessToken(result.AccessToken, result.ExpiresOn);
return token;
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
} |
@christothes May I send a little reminder about my latest workaround suggestion? |
Hi @WolfgangHG - yes, that looks better. |
Thanks for the feedback. Here is a slightly improved code snippet: In the form button click: private MyTokenCredential mtc;
private async void buttonDoSomething_Click(object sender, EventArgs e)
{
if (this.mtc == null)
{
mtc = new MyTokenCredential(this.textBoxTenant.Text, this.textBoxClientID.Text, this);
}
string[] scopes = new string[] { "Calendars.Read" };
GraphServiceClient graphClient = new GraphServiceClient(mtc, scopes);
Calendar cal = await graphClient.Me.Calendar.GetAsync();
MessageBox.Show(this, "Calendar ID: " + cal.Id);
} Class "MyTokenCredential" looks like this: public class MyTokenCredential : TokenCredential
{
private IPublicClientApplication clientApp;
public MyTokenCredential(string _tenant, string _clientId, Form _formParent)
{
this.clientApp = PublicClientApplicationBuilder.Create(_clientId)
.WithDefaultRedirectUri()
.WithTenantId(_tenant)
.WithWindowsEmbeddedBrowserSupport()
.WithParentActivityOrWindow ( () => _formParent.Handle)
.Build();
}
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
AuthenticationResult result;
try
{
var accounts = await this.clientApp.GetAccountsAsync();
result = await this.clientApp.AcquireTokenSilent(requestContext.Scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
result = await this.clientApp.AcquireTokenInteractive(requestContext.Scopes).ExecuteAsync();
}
AccessToken token = new AccessToken(result.AccessToken, result.ExpiresOn);
return token;
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
} |
@christothes I try one last time to convince you to add some callback that allows customization of the "IPublicClientApplication" that is generated internally in Azure.Identity ;-). Is there any chance that you add e.g. a "BeforeBuild" func to InteractiveBrowserCredentialOptions or InteractiveBrowserCredential so that I could create a "IPublicClientApplication" with "WithWindowsEmbeddedBrowserSupport"? If this is not possible, I suggest to add the findings of this ticket to some wiki page, as others might be affected by the fact that Azure.Identity does not use the embedded webview since 1.11.4. But Readme.md does not seem the proper place to do so. I found https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/tokencredentials.md in the GraphSDK project which has sample snippets. |
@christothes So there is nothing to be done about this in Azure.Identity? Shall I close this issue? |
Yes, unfortunately there is no change we can make in Azure.Identity for this issue. |
Library name and version
Azure.Identity 1.11.4
Query/Question
I use the package Microsoft.Graph to access the Microsoft Graph API.
This is a problem that came up with the Azure.Identity update to 1.11.4 (which updated from "Microsoft.Identity.Client" 4.60.3 to 4.61.3): previously, it used "Microsoft.Web.WebView2" and the azure login screen was shown in a WebView2 embedded browser as a modal dialog.
Now, the default system browser opens as a separate application, which is less comfortable for users. Also, it just hangs when you close the browser tab.
I have no idea how to bring back the old behavior.
I create the GraphServiceClient this way (using classes from "Azure.Identity"):
I tried to force the WebView2 control:
But this code results in an exception:
InteractiveBrowserCredential authentication failed: To enable the embedded webview on Windows, reference Microsoft.Identity.Client.Desktop and call the extension method .WithWindowsEmbeddedBrowserSupport().
Adding the package "Microsoft.Identity.Client.Desktop" does not bring me further, as the extension method "PublicClientApplicationBuilder.WithWindowsEmbeddedBrowserSupport" requires an application builder, and I have not idea how to use this application builder with a GraphServiceClient and Azure.Identity. This probably happens internally?
Attached sample contains two small projects for Azure.Identity 1.11.3 and 1.11.4 - you just need an Azure tenant and a Application Client ID, the click the "Do something" button to show the login form.
The project for 1.11.4 also contains the commented "BrowserCustomization" code snippet which would result in an error.
AzureIdentityTest.zip
Environment
No response
The text was updated successfully, but these errors were encountered: