The WebAuthenticationBroker doesn't seem to be able to handle navigation to my ms-app://. Just throws this ugly error as you will see below.
Steps
Call AuthenticateAsync(), including callback uri obtained at runtime: WebAuthenticationBroker.GetCurrentApplicationCallbackUri()
Go through authorize process, hit Allow.
Instead of returning, the broker shows the page Can't connect to service. We can't connect to the service you need right now. Unable to do anything, so I hit the Back button visible.
Debugger breaks on catch: "The specified protocol is unknown. (Exception from HRESULT: 0x800C000D)"
The callback for WebAuthenticationBroker.AuthenticateAsync() is received (according to Fiddler4 & the Event Viewer) but it throws the aforementioned exception as if it doesn't know how to interpret the ms-app:// protocol.
All examples imply my code should work but I think there's something less obvious causing an issue.
Code
private static string authorizeString =
"https://api.imgur.com/oauth2/authorize?client_id=---------&response_type=token";
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
var war = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.UseTitle
, startUri);
// Imgur knows my redirect URI, so I am not passing it through here
if (war.ResponseStatus == WebAuthenticationStatus.Success) {
var token = war.ResponseData;
}
} catch (Exception e) { throw e; }
}
Event Viewer log excerpts (chronological order)
For information on how I obtained this, read the following MSDN: Web authentication problems (Windows). Unfortunately this is the only search result when querying authhost.exe navigation error.
Information: AuthHost redirected to URL: <ms-app://s-1-15-2-504558873-2277781482-774653033-676865894-877042302-1411577334-1137525427/#access_token=------&expires_in=3600&token_type=bearer&refresh_token=------&account_username=------> from URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with HttpStatusCode: 302.
Error: AuthHost encountered a navigation error at URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with StatusCode: 0x800C000D.
Information: AuthHost encountered Meta Tag: mswebdialog-title with content: <Can't connect to the service>.
Thanks for reading, Stack. Don't fail me now!
Afaik, you need to pass the end URL to AuthenticateAsync even if you assume that the remote service knows it.
The way WebAuthenticationBroker works is like the following: you specify an "endpoint" URL and when it encounters a link that starts with this URL, it will consider the authentication process complete and doesn't even try navigating to this URL anymore.
So if you specify "foo://bar" as callback URI, navigating to "foo://bar" will finish the authentication, as will "foo://barbaz", but not "foo://baz".
Resolved! #ma_il helped me understand how the broker actually evaluates the redirect callback and it led me back to square one where I realized I assumed WebAuthenticationOptions.UseTitle was the proper usage. Not so. Up against Imgur's API using a token, it requires WebAuthenticationOptions.None and it worked immediately.
As an example to future answer-seekers, here's my code.
private const string clientId = "---------";
private static Uri endUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
private static string authorizeString = "https://api.imgur.com/oauth2/authorize?"
+ "client_id="
+ clientId
+ "&response_type=token"
+ "&state=somestateyouwant"
+ "&redirect_uri="
+ endUri;
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
WebAuthenticationResult webAuthenticationResult =
await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None
, startUri
, endUri);
if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success) {
string token = webAuthenticationResult.ResponseData;
// now you have the token
}
} catch { throw; }
}
Related
Under Asp.Net MVC I used to build the body of my mailing messages on a MVC view which the system downloaded through System.Net.HttpWebRequest.
Now that I am migrating to Blazor server, I can browse the page but if I try to download it to fill the body of the message I always get next body:
Loading... An unhandled exception has occurred. See browser dev
tools for details. Reload X
I tried both through a Blazor page and through a Cshtml razor page. My browser can see both successfully but I always get the same exception.
This is the code I use to download the page:
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var contentType = response.Content?.Headers?.ContentType?.MediaType;
if (contentType == "application/json" | contentType == "text/html")
{
string responseText = await response.Content?.ReadAsStringAsync() ?? "";
if (typeof(T) == typeof(string))
retval.Value = (T)(object)responseText;
else
retval.Value = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText);
}
else
{
byte[] result = await response.Content.ReadAsByteArrayAsync();
retval.Value = (T)Convert.ChangeType(result, typeof(T));
}
}
}
I finally discovered the problem was on Program.cs from my .Net7 Server side Blazor app.
I was registering HttpClient as follows:
var http = new HttpClient();
builder.Services.AddScoped(sp => http);
This was Ok to access API data, but for some reason if you try to download a Html page source it throws a System.ObjectDisposedException: Cannot access a disposed object.
The right way to register the service to avoid this exception is:
builder.Services.AddHttpClient();
The problem is I no longer have a http variable in Program.cs, which I used to preload data from Api before the index page was ready.
Need a bit more detail:
What line threw the exception?
What was the exception?
What was value of string responseText or byte[] result?
I suspect either the DeserializeObject or Convert.ChangeType call failed. You should debug this; the answer will probably become apparent as you step through the code.
I use HttpAysnClient to do http requests, and I found when I throw an exception in the failed callback, the next request always be failed, how to fix it?
I use maven dependency: 'org.apache.httpcomponents:httpasyncclient:4.1.5'.
my java test code:
CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
try {
httpclient.start();
AtomicBoolean fireException = new AtomicBoolean(false);
while (true) {
try {
String url;
if (fireException.compareAndSet(false, true)) {
url = "http://localhost:8080"; // throw Connection refused
} else {
url = "http://www.apache.org/";
}
final HttpGet request2 = new HttpGet(url);
httpclient.execute(request2, new FutureCallback<HttpResponse>() {
public void completed(final HttpResponse response2) {
System.out.println("completed, " + request2.getRequestLine() + "->" + response2.getStatusLine());
}
public void failed(final Exception ex) {
System.out.println("failed, " + request2.getRequestLine() + "->" + ex);
throw new RuntimeException();
}
public void cancelled() {
System.out.println(request2.getRequestLine() + " cancelled");
}
});
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
TimeUnit.SECONDS.sleep(1);
}
}
} finally {
httpclient.close();
}
exception in the next requests: java.util.concurrent.CancellationException: Request execution cancelled
I can confirm same behavior with version 4.1.5.
I must confess it is quite surprising to see an application uncontrolled exception shutting down the whole client unexpectedly. In the context of an application reusing same client instance in multiple places, means the application client gets completely unsuable, with catastrophic consequences for the service.
You can use the "isRunning" method to evaluate if the client is under this situation, and potentially try to recreate the client again. But it is definately incovenient to see the client being shutdown like this.
After exercising the client with different conditions (error responses, slow responses...), the only way to reproduce this is to point to an invalid endpoint where no server is running. This is the condition presented in the original example.
I think I found the issue here https://jar-download.com/artifacts/org.apache.httpcomponents/httpasyncclient/4.1.5/source-code/org/apache/http/impl/nio/client/InternalIODispatch.java
You can see onException doesn't have a try/catch block to properly handle exceptions from the application.
I have confirmed this issue is fixed in Httpclient5 5.1.3. So other than fixing your application code to avoid uncontrolled exceptions, the solution is to migrate into the new Httpclient5 lib version.
you can see doc in https://hc.apache.org/httpcomponents-client-5.1.x/migration-guide/migration-to-async-simple.html
and if you want to use CloseableHttpClient you must start it client.start();
the
Lately I joined a project that is using Azure AD Open ID connect authentication code to authenticate with the ASP.Net Core web application.
When I am trying to run it locally I am facing issues with retrieving info with the GetAccountAsync method (Return null). From what I read, I think the code is missing a caching helper to cache the user/application tokens.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
var account = await _app.GetAccountAsync(userId);
try
{
var result = await _app.AcquireTokenSilent(_scopes, account).ExecuteAsync();
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
If anyone has any idea what I could do to fix this issue Ill be happy to know :)
Thank you!
I'm using FOSRestBundle for my REST API and so far it has been a great tool. I use HTTP Basic Auth and in most of the cases it works just fine. However, I have problems with the bundle's exception behaviour when bad credentials are submitted. When handling exceptions (via the integrated authentication handlers or the exception mapping configuration), the bundle always gives me a response with the correct HTTP status and JSON/XML content similar to this:
{
"code": 401,
"message": "You are not authenticated"
}
This is fine, it also works when no authentication information is submitted at all. However, when submitting bad credentials (e.g. unknown username or incorrect password) I get the HTTP code 401 Bad credentials (which is fine) with an empty message body. Instead, I would have expected something similar to the JSON above.
Is it a bug or a configuration issue on my side? I would also love to know how these kinds of authentication errors are exactly handled by the bundle, since overriding the BadCredentialsException's status code in the codes section of the bundle's exception configuration section seems to be ignored.
Thanks!
Alright, after digging into the bundle's code some more, I figured it out. The problem results from the way bad credentials are handled by Symfony's HTTP Basic Authentication impementation. The 401 Bad Credentials response is a custom response created by BasicAuthenticationEntryPoint, which is called by the BasicAuthenticationListener's handle function, immediately after an AuthenticationException has been thrown in the same function. So there is no way of catching this exception with a listener:
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
return;
}
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) {
return;
}
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Basic Authentication Authorization header found for user "%s"', $username));
}
try {
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
$this->securityContext->setToken($token);
} catch (AuthenticationException $failed) {
$this->securityContext->setToken(null);
if (null !== $this->logger) {
$this->logger->info(sprintf('Authentication request failed for user "%s": %s', $username, $failed->getMessage()));
}
if ($this->ignoreFailure) {
return;
}
$event->setResponse($this->authenticationEntryPoint->start($request, $failed));
}
}
The entry point's start function creates the custom response, with no exceptions involved:
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
$response->setStatusCode(401, $authException ? $authException->getMessage() : null);
return $response;
}
The fist if-clause in the handle function above also explains why it works in the case of "no user credentials at all", since in that case, the listener just stops trying to authenticate the user, and therefore an exception will be thrown by Symfony's firewall listeners (not quite sure where exactly), so FOSRestBundle's AccessDeniedListener is able to catch the AuthenticationException and do its thing.
You can extend AccessDeniedListener and tell FOSRestBundle to use your own listener with the parameter %fos_rest.access_denied_listener.class%. (service definition)
parameters:
fos_rest.access_denied_listener.class: Your\Namespace\For\AccessDeniedListener
Then add an additional check for BadCredentialsException and emmit an HttpException with the desired code/message similar to the check for AuthenticationException at Line 70.
I need to authenticate a user in a page based on the remember me cookie,
inspired by this site: Tutorial for checking spring authentication,
I came up with a solution for checking the authentication.
Changes made in my application
applicationContext-security.xml:
<intercept-url pattern='/**AuthenticationChecker.html' access="ROLE_ADMIN"/>
...
<form-login login-page="/Login.html" authentication-failure-url="/Login.html" always-use-default-target="true" default-target-url="/Main.html"/>
Gwt code:
try
{
RequestBuilder rb = new RequestBuilder(
RequestBuilder.POST, "AuthenticationChecker.html");
rb.sendRequest(null, new RequestCallback()
{
public void onError(Request request, Throwable exception)
{
RootPanel.get().add(new HTML("[error]" + exception.getMessage()));
}
public void onResponseReceived(Request request, Response response)
{
RootPanel.get()
.add(new HTML("[success (" + response.getStatusCode() + "," + response.getStatusText() + ")]"));
}
}
);
}
catch (Exception e)
{
RootPanel.get().add(new HTML("Error sending request " + e.getMessage()));
}
AuthenticationChecker.html is a simple blank html page,
from what I understand, as AuthenticationChecker.html requires role as admin, I should have got a 401 Unauthorized if remember me cookie was not present and a 200 OK if the user was authenticated and his cookie was present.
However, the output always shows: [success (200,OK)]
To cross check, i simply typed authenticaionChecker.html (without logging in) and it returned back to Login.html indicating that spring is indeed authenticating the user.
Am I doing something wrong here ?
If you look at the tutorial, you'll see that a 401 is only returned when you're using Basic Authentication. With form-based authentication, you have to check the response text for an error message. For example:
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() != Response.SC_OK) {
onError(request, new RequestException(response.getStatusText() + ":\n" + response.getText()));
return;
}
if (response.getText().contains("Access Denied")) {
Window.alert("You have entered an incorrect username or password. Please try again.");
} else {
// authentication worked, show a fancy dashboard screen
}
}