How can I use dotnetbrowser doing the following in a winform application?
Create a listener that listen for callbacks to a specific redirect url.
Open url in dotnetbrowser. The url makes the callback to the redirect url in another thread
The listener catches the response from the callback.
I can do this with an ordinary webbrowser, but I would like it to be silent. That's why I try to use dotnetbrowser instead.
Is dotNetBrowser a good choice for this, or is there a better option?
This is from my test code with a non silent webbrowser. First I create a listener that listen to a redirectUri:
var listener = new HttpListener();
listener.Prefixes.Add(redirectURI);
listener.Start();
Then I start the url in a webbrowser:
Process p = Process.Start(url);
The started url will send a callback to the redirectUri. The listener will get it.
var context = await listener.GetContextAsync(); ;
string formData = string.Empty;
using (var body = context.Request.InputStream)
{
using (var reader = new System.IO.StreamReader(body, context.Request.ContentEncoding))
{
formData = reader.ReadToEnd();
}
}
listener.Close();
I found a solution with help from dotnetbrowser support site.
This is the winform constructor in my new test project:
public Form1()
{
webView = new BrowserView() { Dock = DockStyle.Fill };
Task.Run(() =>
{
engine = EngineFactory.Create(new EngineOptions.Builder
{
RenderingMode = RenderingMode.HardwareAccelerated,
LicenseKey = "your license key here"
}
.Build());
browser = engine.CreateBrowser();
})
.ContinueWith(t =>
{
webView.InitializeFrom(browser);
var listener = new HttpListener();
listener.Prefixes.Add(redirectURI);
listener.Start();
browser.Navigation.LoadUrl(url);
var context = listener.GetContextAsync().GetAwaiter().GetResult();
//Get data from redirectUri. You find this code from test example above, but not really relevant for the problem.
var formData = GetRequestPostData(context.Request);
listener.Close();
}, TaskScheduler.FromCurrentSynchronizationContext());
InitializeComponent();
FormClosing += Form1_FormClosing;
Controls.Add(webView);
this.Visible = false;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
browser?.Dispose();
engine?.Dispose();
}
Related
For a Winforms Desktop application I will use the authorization code flow with PKCE. As Identity provider I use IdentityServer and as client library OicdClient.
Next step I have to decide which Browser to use for the user login:
SystemBrowser
Extended WebBrowser
For SystemBrowser speaks the simple/clear implementation of the flow.
For Extended WebBrowser speaks that some user may have no SystemBrowser. But the WebBrowser is an older IE version? and is it allowed to use for a secure authentication?
Nevertheless I tried the "Extended WebBrowser" Sample and stumble integrating it in to my prototype Environment with own IS4 server. Therefore I need some clarity with the code flow and redirect.
I already had implemented this authorization code flow with pure .Net classes, but using OicdClient makes me little confusing(in the beginning like a black box).
My question is how does the redirect work with this libraries and who are responsible for redirect and who are responsible to receive the redirect with the code (to exchange for access token)?
The code flow has following steps (without details like clientID, PKCE ...):
Send a code request to IS4
IS4 Response with a login page (shown in a Browser)
After successful login IS4 sends to redirect URL with code
A HttpListener receives this redirect with code and
Send a request to IS4 with the code to receive a access token
With OidcClient and using the Automatic Mode:
var options = new OidcClientOptions
{
Authority = "https://demo.identityserver.io",
ClientId = "native",
RedirectUri = redirectUri,
Scope = "openid profile api",
Browser = new SystemBrowser()
};
var client = new OidcClient(options);
var result = await client.LoginAsync();
Here is to much magic for me. Only a call to LoginAsync() makes it work...
An important point seems to be the Browser property of the options with the IBrowser interface and its implementation of this method:
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken)
{
using (var listener = new LoopbackHttpListener(Port, _path))
{
OpenBrowser(options.StartUrl);
try
{
var result = await listener.WaitForCallbackAsync();
if (String.IsNullOrWhiteSpace(result))
{
return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." };
}
return new BrowserResult { Response = result, ResultType = BrowserResultType.Success };
}
catch (TaskCanceledException ex)
{ ....}
}
}
if I try to map to the flow steps:
Login page: OpenBrowser(options.StartUrl);
Redirect will be done by IS4? The SystemBrowser from sample does not do this.
Receive the code: await listener.WaitForCallbackAsync();
1 and 5 are probably done by the OicdClient. This Example is fairly clear, need confimation that redirect is done by IS4.
The implementation in the other example Extended WebBrowser
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken))
{
using (var form = _formFactory.Invoke())
using (var browser = new ExtendedWebBrowser()
{
Dock = DockStyle.Fill
})
{
var signal = new SemaphoreSlim(0, 1);
var result = new BrowserResult
{
ResultType = BrowserResultType.UserCancel
};
form.FormClosed += (o, e) =>
{
signal.Release();
};
browser.NavigateError += (o, e) =>
{
e.Cancel = true;
if (e.Url.StartsWith(options.EndUrl))
{
result.ResultType = BrowserResultType.Success;
result.Response = e.Url;
}
else
{
result.ResultType = BrowserResultType.HttpError;
result.Error = e.StatusCode.ToString();
}
signal.Release();
};
browser.BeforeNavigate2 += (o, e) =>
{
var b = e.Url.StartsWith(options.EndUrl);
if (b)
{
e.Cancel = true;
result.ResultType = BrowserResultType.Success;
result.Response = e.Url;
signal.Release();
}
};
form.Controls.Add(browser);
browser.Show();
System.Threading.Timer timer = null;
form.Show();
browser.Navigate(options.StartUrl);
await signal.WaitAsync();
if (timer != null) timer.Change(Timeout.Infinite, Timeout.Infinite);
form.Hide();
browser.Hide();
return result;
}
}
Done by: browser.Navigate(options.StartUrl);
Redirect by IS4
Receive of code in event handle: NavigateError ???
Is here something wrong?
On IS4 the AccountController.Login is called
that calls /connect/authorize/callback? with the redirect_uri.
But this doesn't come to BeforeNavigate2. instead NavigateError event appears where the result set to:
result.ResultType = BrowserResultType.Success;
result.Response = e.Url;
Current best practice is to use the user's default web browser and not to embed a browser component. As for how to implement that - since you can't intercept browser navigation events using this approach you'd need to implement an HTTP listener that can accept the POST request from your identityserver4 implementation.
Have a read of this: https://auth0.com/blog/oauth-2-best-practices-for-native-apps/
And this RFC: https://www.rfc-editor.org/rfc/rfc8252
I'm unable to get a custom cookie authentication handler working with IdentityServer4. I'm using ASP.NET Core Identity and have followed the official guide: https://identityserver4.readthedocs.io/en/release/topics/signin.html
I need to override the CookieAuthenticationEvents.ValidatePrincipal and CookieAuthenticationEvents.SignedIn event handlers.
I've written a class that inherits CookieAuthenticationEvents and overrides the two event handlers.
I'm assigning it to a custom cookie handler via:
var auth = services.AddAuthentication("MyCookies");
auth.AddCookie("MyCookies", options =>
{
options.Events = new RealtimeStatusCookieAuthEvents(Configuration);
});
Here's my code:
https://gist.github.com/Amethi/f3411038a9447d274c0b721698fc5e63
The event handlers don't fire, i.e. I'm expecting them to fire for each request (due to ValidatePrincipal) and when I come back to the site after closing the browser and sign-in using cookie authentication (SignedIn).
Anyone know what I'm doing wrong?
Update:
Even simplifying it as follows doesn't help. The event handlers don't fire.
var auth = services.AddAuthentication("CustomCookies").AddCookie("CustomCookies", options =>
{
options.Events = new CookieAuthenticationEvents
{
OnSignedIn = context =>
{
Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
"OnSignedIn", context.Principal.Identity.Name);
return Task.CompletedTask;
},
OnValidatePrincipal = context =>
{
Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
"OnValidatePrincipal", context.Principal.Identity.Name);
return Task.CompletedTask;
},
};
});
I managed to make my custom cookie authentication handler work by using the ConfigureApplicationCookie extension.
builder.Services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "IdentityServer.Cookie";
config.EventsType = typeof(CustomCookieAuthenticationHandler);
config.LoginPath = "/Account/Login";
});
And register the CustomCookieAuthenticationHandler handler
builder.Services.AddScoped<CustomCookieAuthenticationHandler>();
This is the handler implementation:
public class CustomCookieAuthenticationHandler: CookieAuthenticationEvents
{
private readonly IUserRepository _userRepository;
public CustomCookieAuthenticationEvents(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
// Your cookie authentication logic.
}
}
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0
I'm using CefSharp (47) to render a webpage from a host that I have no control over, and I want to make some additional CSS tweaks to those provided by the host.
Reading up on various topics across GitHub (https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp.Example/CefSharpSchemeHandlerFactory.cs), and here (CefSharp custom SchemeHandler), I wrote a custom scheme handler accordingly:
public class CustomSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "custom";
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
Console.WriteLine(request.Url);
if (schemeName.ToLower() == SchemeName.ToLower())
{
// Do some stuff
}
return null;
}
}
I attempt to bind it in my application in the following manner:
CefSettings settings = new CefSettings();
settings.CachePath = browserCachePath;
settings.RegisterScheme(new CefCustomScheme()
{
SchemeName = CustomSchemeHandlerFactory.SchemeName,
SchemeHandlerFactory = new CustomSchemeHandlerFactory()
});
Cef.Initialize(settings);
The application then browses to the appropriate website, and uses the 'LoadingStateChanged' event to then fire off some JavaScript to inject the CSS file I want to load:
string linkText = "<link rel=\u0022stylesheet\u0022 type=\u0022text/css\u0022 href=\u0022custom://custom.css\u0022>";
var jsFunctionText = string.Format("(function() {{ $('head').append('{0}'); return true;}}) ();", linkText);
var injectionTask = await _myBrowser.GetMainFrame().EvaluateScriptAsync(jsFunctionText, null);
...which succeeds.
But my custom resource handler 'Create' event is never fired.
I can only presume that the handler isn't being registered properly, so I'd appreciate any advice/help in getting this working properly!
Thanks!
I have the following simple application page that uses the phone camera to upload the taken photo to azure blob:
public partial class AddReport : PhoneApplicationPage
{
// blobs stuff
string storageAccount = "MYACCOUNT";
string storageKey = "MYKEY";
string blobServiceUri = "http://MYACCOUNT.blob.core.windows.net";
CloudBlobClient blobClient;
private Report newReport;
public AddReport()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
//base.OnNavigatedTo(e);
newReport = new Report();
var credentials = new StorageCredentialsAccountAndKey(storageAccount, storageKey);
blobClient = new CloudBlobClient(blobServiceUri, credentials);
}
private void TakePhotoClick(object sender, EventArgs eventArgs)
{
//The camera chooser used to capture a picture.
CameraCaptureTask ctask;
//Create new instance of CameraCaptureClass
ctask = new CameraCaptureTask();
//Create new event handler for capturing a photo
ctask.Completed += new EventHandler<PhotoResult>(ctask_Completed);
//Show the camera.
ctask.Show();
}
void ctask_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK && e.ChosenPhoto != null)
{
WriteableBitmap CapturedImage = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
UploadToBlobContainer(e.ChosenPhoto);
}
else
{
//user decided not to take a picture
}
}
private void UploadToBlobContainer(System.IO.Stream stream)
{
string containerName = "reportsPhotos";
var container = blobClient.GetContainerReference(containerName);
container.CreateIfNotExist(true, r =>
Dispatcher.BeginInvoke(() =>
{
var blobName = "report" + newReport.ReportId.ToString();
var blob = container.GetBlobReference(blobName);
blob.Metadata["ReportId"] = newReport.ReportId.ToString();
blob.UploadFromStream(stream, r2 =>
Dispatcher.BeginInvoke(() =>
{
newReport.Photo = container.Uri + "/" + blobName;
}));
}));
}
}
This is a simple case and I am not using SAS to authenticate, instead I save the key in the app itself (this is only for testing purposes) and also my blobs are publicly available.
when I run in debug mode it seems that everything is working, but the photo doesn't get uploaded to the blob. Also, I don't know how I can debug this to see if there was any error from the blob service.
Can anyone tell me what might be wrong ?
EDIT1: it seems that the container is not being created either. i've confirmed this using azure blob explorer
EDIT2: I am getting a System.Net.WebException : "The remote server returned an error: NotFound."
After long hours I have finally discovered that the problem was with this line:
string containerName = "reportsPhotos";
According to here all letters in a container name must be lowercase.
Changing it to reportsphotos solved the issue
That was time well spent.
Can you try just doing it like this instead:
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));
// Create the blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");
// Retrieve reference to a blob named "myblob"
CloudBlob blob = container.GetBlobReference("myblob");
// Create or overwrite the "myblob" blob with contents from a local file
using (var fileStream = System.IO.File.OpenRead(#"path\myfile"))
{
blob.UploadFromStream(fileStream);
}
This is from:
http://www.windowsazure.com/en-us/develop/net/how-to-guides/blob-storage/#upload-blob
I have a Silverlight 4.0 application that is making RESTful calls to an MVC3 application using the Hammock API on the client to issue the RESTful service codes.
The problem is that whether the request.Method is set to WebMethod.Get or WebMethod.Post, the request that is sent is a POST. What am I doing wrong?
private IAsyncResult GetServerList()
{
var callback = new RestCallback((restRequest, restResponse, userState) =>
{
// There is some working callback code here. Excluded for clarity.
}
);
var request = new RestRequest();
request.Method = WebMethod.Get;
request.Path = "ServerList";
return _restClient.BeginRequest(request, callback);
}
Try setting the request type on RestClient.
var restClient = new RestClient
{
Method = WebMethod.Get
};
Or from your example:
private IAsyncResult GetServerList()
{
var callback = new RestCallback((restRequest, restResponse, userState) =>
{
// There is some working callback code here. Excluded for clarity.
}
);
var request = new RestRequest();
request.Path = "ServerList";
_restClient.Method = WebMethod.Get;
return _restClient.BeginRequest(request, callback);
}