We're using an asp:TreeView configured with lazy loading. The callback method assigned for OnTreeNodePopulate throws an exception if the user has been logged out since the page was loaded. What we want to do is to direct the user to the login page.
First attempt was to catch the exception on the server and try Response.Redirect(...), but that doesn't work because you can't redirect within a callback.
I've tried various other approaches, including using ClientScript.RegisterStartupScript(...) but that doesn't seem to work for OnTreeNodePopulate.
If there was some way we could hook into the callback event handling on the client side then it would be easy, but the TreeView doesn't seem to offer anything here.
Suggestions?
OK, I have a workaround, though I'm still eager to hear other suggestions as this is total filth:
In my callback I catch the exception with the following block:
catch (Exception ex)
{
if (IsAuthException(ex))
{
e.Node.ChildNodes.Add(new TreeNode(#"<script type=""text/javascript"">window.location.href = 'Default.aspx';</script>"));
}
else
{
throw;
}
}
Fortunately the TreeView doesn't escape anything so the JS gets executed by the browser and directs the user to the login page.
Related
Please see the code below:
public async Task<List<Person>> GetAllPeople()
{
var uri = API.People.GetAllPeople(_remoteServiceBaseUrl);
try
{
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<List<Person>>(responseString);
return response;
}
catch (Exception e)
{
//Redirect to error webpage
}
}
I am using Identity Server 4 for authentication and authorisation facilities in the web app and web api.
Say the Web API requires administrator users only and the user calling the web api is not an administrator, then a HttpRequestException is caught by the MVC caller. How should this scenario be handled? Is it enough just to put an Administrator Authorize attribute on the MVC method (GetAllPeople) and forget it or is there a more elegant way to do this i.e. remove the try catch?
It is not convenient to redirect user to error page in every method. You can add exception handler middleware to pipeline and configure it to catch error, log it and show default error page. Read this for more details:
UseExceptionHandler Method
StatusCodePagesExtensions.UseStatusCodePagesWithReExecute
As for you main question - it will be enough to user AuthorizeAttribute in case if default error handling is properly configured.
I've added LinkedIn as a provider. I have implemented the login and register with LinkedIn without any issue. In the use case where the user CANCELS from within the provider Pages (either linkedIn login or cancels the authorization of the app) the identity middleware seems to throw an unhandled exception:
An unhandled exception occurred while processing the request.
Exception: user_cancelled_login;Description=The user cancelled LinkedIn login
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
System.Exception: user_cancelled_login;Description=The user cancelled LinkedIn login
Exception: An error was encountered while handling the remote login.
The provider setup in startup defines the callback:
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c =>
{
c.ClientId = Configuration["Authentication:LinkedIn:ClientId"];
c.ClientSecret = Configuration["Authentication:LinkedIn:ClientSecret"];
c.Scope.Add("r_basicprofile");
c.Scope.Add("r_emailaddress");
c.CallbackPath = "/signin-linkedin";
....
And As I have said the middleware seems to handled ALL other cases except where the user cancels within the LinkedIn pages. The return URL from LinkedIn looks correct:
https://localhost:44372/signin-linkedin?error=user_cancelled_login&error_description=The+user+cancelled+LinkedIn+login&state=CfDJ8MA7iQUuXmhBuZKmyWK9xeAgBBkQvnhf1akLhCIn9bsajCPUf7Wg22oeZBH9jZOIY3YrchMSWZ4dH7NQ1UngLKEuqgU-IHfBglbgJDtS-wc4Z-6DnW66uR0G1ubvNVqatFJHYv17pgqZT98suVkvKgihcJdbNEw7b1ThkuFbn9-5EcYhQ5ln6ImoTgthT8io1DOcCfc_-nBVfOa93a6CpUJTsZc9w93i70jn5dKKXSLntZe0VyRSA0r0PKc5spu5En-0R1rxiLjsjo4dy89PV3A
But never gets to my ExternalCallback controller method where the other cases like successful login/authorization are handled??
I'm wondering if this is working for anyone else with 3rd part providers?
There's a Github issue that explains what's happening here in more detail, with a bit of information as to why it's happening and even an indication that this won't be "fixed":
Handling the RemoteFailure event is the right thing to do. We should update our docs/samples to show how to handle that event and at least show a more appropriate message to the user. The error page sample could include a link to enabled the user to try logging in again.
Unfortunately it's difficult to implement this event in a very generic way that's also super useful because each remote auth provider has its own behavior for different types of failures.
The workaround for this (as quoted above) is to handle the RemoteFailure event:
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c => {
// ...
c.Events.OnRemoteFailure = ctx =>
{
// React to the error here. See the notes below.
return Task.CompletedTask;
}
// ...
});
ctx is an instance of RemoteFailureContext, which includes an Exception property describing what went wrong. ctx also contains a HttpContext property, allowing you to perform redirects, etc, in response to such exceptions.
I've found the following to work well for me, based on this and similar to Kirk Larkin's answer. The part that took a little figuring out was where to redirect to, without causing problems for subsequent login attempts.
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c =>
{
...
c.Events = new OAuthEvents()
{
OnRemoteFailure = (context) =>
{
context.Response.Redirect(context.Properties.GetString("returnUrl"));
context.HandleResponse();
return Task.CompletedTask;
}
};
};
I am using a API with a limit, there is a high possibility that I could hit the limit.
I am wondering how people handle this? Do they check if they hit the limit and then throw an exception? If so what type of exception?
Are there any best practices?
I am able to see if I hit the limit like below:
if (!string.IsNullOrEmpty(result.error))
{
// we have hit a limit
}
This API is used for a MVC application.
I am caching the ActionResult methods with the API content ([OutputCache]). If the Action method does not recieve the API result then the view will be empty, but if i throw something it will end up on the custom errors page.
You should first log what happened using the information you receive from result.error and then create a custom exception and throw it including the original error message.
if (!string.IsNullOrEmpty(result.error))
{
// we have hit a limit
// log error
// throw new MyException(result.error);
}
Normally you can't do much in this type of error so you log it, throw the exception and it's somebody's else problem. When the exception is caught, you could decide, based on the exception type, to wait and retry.
Within my site master I have an area where I would like to display status (info, success, error, warning) messages.
In my code behind I would like to make calls such as:
MessageSuccess("Some success message");
MessageSuccess("Another success message");
MessageWarning("Warning message");
and then have these messages all display when the page is next rendered.
I have tried a couple of approaches where I:
Save a structure in Session
Read the structure in Page_PreRender() and update some HTML controls
Clear the structure in Page_UnLoad()
However some of the time the messages show just fine, but some of the time by the time PreRender comes around Page_Unload() has been called and nothing displayed because the structure is empty.
Has anyone got a generic working solution that I can use with my WebForms project to "nicely" display status messages?
Once I was using something very close to example described in Displaying a Custom Error Message section of the Working with Partial-Page Rendering Events. It works nicely if you are dealing with asynchronous postbacks. The only downside is that it was designed for exception handling and not for success/warning/etc. notifications.
There is a property AsyncPostBackErrorMessage on ScriptManager which you can use to pass any text message back to the client. Client will retrieve the message in endRequest event handler:
Server code:
// inside Page code:
var scriptManager = ScriptManager.GetCurrent(this /* assuming that this is a Page instance */);
scriptManager.AsyncPostBackErrorMessage = "Hello world";
Client code:
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
function EndRequestHandler(sender, args) {
if (args.get_error() != undefined) {
var errorMessage = args.get_error().message;
args.set_errorHandled(true);
// Do whatever you need to display the message
}
}
Another possible solution would be to add client function calls to the page using ScriptManager.RegisterStartupScript. I can imagine that you can have a NotificationHandler JavaScript object which would have a success, warning, error, etc. functions which you will call from the server side:
protected void Page_PreRender(object sender, EventArgs e)
{
ScriptManager.RegisterStartupScript(this, GetType(), "<Some unique key for script>", "NotificationHandler.success(\"Hello world\")", true);
}
I am currently looking into adding dropbox to my c# software. I am using spring.social from https://github.com/SpringSource/spring-net-social-dropbox.
I have the following code to do the authentication
private void authenticateDropbox()
{
try
{
DropboxServiceProvider dropboxServiceProvider = new DropboxServiceProvider(dropboxAppKey, dropboxAppSecret, AccessLevel.AppFolder);
//lblStatus.Content = "Getting request Token";
OAuthToken oauthToken = dropboxServiceProvider.OAuthOperations.FetchRequestTokenAsync(null, null).Result;
//lblStatus.Content = "Request token retrieved";
OAuth1Parameters parameters = new OAuth1Parameters();
string authenticateUrl = dropboxServiceProvider.OAuthOperations.BuildAuthorizeUrl(oauthToken.Value, parameters);
//lblStatus.Content = "Redirecting user for authentication";
Process.Start(authenticateUrl);
}
catch (AggregateException ex)
{
MessageBox.Show("AggregateException: Failed to authenticate\n\n" + ex.Message, "Authentication Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show("General Exception: Failed to authenticate\n\n" + ex.Message, "Authentication Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
I am successfully loading my default browser and I can allow the app and I get the success page. In the same after it has done the Process.start() method it asks the user to press enter to continue after the success page has been shown, however, I am doing it in a wpf application and I don't really want to have to ask the user to press a button to make my program continue after authorisation from the browser.
I saw in the FetchRequestTokenAsync function there is a callback parameter but I am not sure how to do this or even if this is what I want.
Basically what I want is when the browser says that authorisation was successful it closes the browser and C# picks up that it was successfully authorised and then continues.
Thanks for any help you can provide.
Process.Start() is used in the Console quick start, but if you are using a WPF application, you should use the WebBrowser control to load the authorization page.
Then register to the Navigating event to know when the success callback page is called.
Check the Windows Phone 7 quick start in the zip package, you can easily port it to WPF.
https://github.com/SpringSource/spring-net-social-dropbox/tree/master/examples/Spring.WindowsPhoneQuickStart/Spring.WindowsPhoneQuickStart