I have a Xamarin app that talks to an API. There is a certain scenario that happens when talking to the API in that a 401 (Unauthorized) exception is returned. This 401 (Unauthorized) is returned on purpose when the user account is made inactive so that even though the users token is still valid on the app they wouldn't be able to get any data back from the API.
I want to be able log the user out of the app, only when a 401 (Unauthorized) exception is thrown.
My API call looks like this:
public async Task<T> GetAsync<T>(string url)
{
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _authToken?.AccessToken ?? this.GetToken().AccessToken);
var json = await _client.GetStringAsync(url);
return json.Deserialize<T>();
}
When the debugger reaches the var json = await _client.GetStringAsync(url); line a 401 (Unauthorized) exception is correctly thrown.
I want to be able to handle this 401 (Unauthorized) exception and log the user out of the app (preferably with an alert informing them of this).
I'm currently debugging on an Android device so I tried adding the following code to the MainActivity class.
protected override async void OnCreate(Bundle bundle)
{
AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironmentOnUnhandledException;
}
private void AndroidEnvironmentOnUnhandledException(object sender, RaiseThrowableEventArgs e)
{
if(e.Exception.InnerException.GetBaseException().Message == "401 (Unauthorized)")
{
}
}
When the error is thrown I check if its a 401 (Unauthorized). It was here that I thought I would then log the user out of the app but I don't think this is the right direction.
Is there a best practice for handing this type of scenario that I am not aware of yet?
You could try to use try catch to warp var json = await _client.GetStringAsync(url) like the following code.
try
{
var json = await _client.GetStringAsync(url)
}
catch (WebException e)
{
using (WebResponse response = e.Response)
{
HttpWebResponse httpResponse = (HttpWebResponse)response;
Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
using (Stream data = response.GetResponseStream())
using (var reader = new StreamReader(data))
{
// text is the response body
string text = reader.ReadToEnd();
if (text == "401 (Unauthorized)")
{
}
}
}
}
Related
I am currently investigating in WebApi 2 Message Handler and how to implement a server side timeout using cancellation tokens. If a cancellation occurs a OperationCanceledException is thrown and handled in my Message Handler. In this case I return a HttpResponseMessage with a adequate HttpStatusCode (HttpStatusCode.RequestTimeout).
I expected that my consuming client (using postman) retrieves this HttpStatusCode, but instead "Could not get any response" is displayed, thus my client aborts without any additional information. Could someone explain to me whats the deal with this behavior? What am I missing?
See following example code:
public class RequestTimeoutHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TimeSpan timeout = TimeSpan.FromSeconds(10.0);
using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout))
using (CancellationTokenSource linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token))
{
try
{
return await base.SendAsync(request, linkedCancellationTokenSource.Token);
}
catch (OperationCanceledException e)
{
return new HttpResponseMessage(HttpStatusCode.RequestTimeout);
}
}
return null;
}
}
My Test Controller Method looks as follows:
[Route("testTimeoutAsyncHandleException"), HttpPost]
public IHttpActionResult TestTimeoutAsynchandle(string hugo, CancellationToken ct)
{
for (int i = 0; i < 500; i++)
{
Thread.Sleep(1000); //sleep 1 sec until exception is thown
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
}
return Ok("yes");
}
turns out, using postman was not my best idea. I created a console application using a httpclient and requested my test method. the httpstatus 408 is returned as expected.
I have oauth token implemented on server side but upon Invalid token or Token expirey i am getting 200 http status code but in response body i have
{"code":"4XX", "data":{"some":"object"}
When i try to read string in interceptor i get okhttp dispatcher java.lang.illegalstateexception closed because response.body().string() must be called only once.
Also i read from here Refreshing OAuth token using Retrofit without modifying all calls that we can use OkHttp Authenticator class but it works only with 401/407 i havent triedn as i will not get this. Is there any way we can customize Authenticator and proceed our logic inside it.
Thank you
If it possible, try to talk with your server side about response codes. Communication is also a very important skill.
If it inpossible, you can modify response codes manually with reflection, it enables okHttp authentication logic.
public OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.authenticator((route, response) -> {
System.out.println("it working");
return null;
})
.addNetworkInterceptor(new UnauthorizedCaseParserInterceptor())
.build();
}
public class UnauthorizedCaseParserInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (isUnauthorizedResponse(response)) {
try {
Field codeField = response.getClass().getDeclaredField("code");
codeField.setAccessible(true);
codeField.set(response, HttpURLConnection.HTTP_UNAUTHORIZED);
} catch (Exception e) {
return response;
}
}
return response;
}
private boolean isUnauthorizedResponse(Response response) {
//parse response...
}
}
Please use this solution only as a last resort.
Today i wanted to switch from GCM to FCM so i set up everything needed and wanted to implement the server side code. I used the gcm4j library and changed it so that the adress goes to https://fcm.googleapis.com/fcm/send.
So im doing the following:
FCM fcm = new FCMDefault(new FCMConfig().withKey(FCMGlobals.FCM_API_KEY));
FCMRequest request = new FCMRequest().withRegistrationId(android.getRegistration())
// .withCollapseKey(collapseKey)
.withDelayWhileIdle(true)
.withDataItem(FCMGlobals.FCM_PARAM_CODE, code)
.withDataItem(FCMGlobals.FCM_PARAM_USER_ID, "" + user.getId())
.withDataItem(FCMGlobals.FCM_PARAM_ADDITION, "" + addition);
ListenableFuture<FCMResponse> responseFuture = fcm.send(request);
Futures.addCallback(responseFuture, new FutureCallback<FCMResponse>() {
public void onFailure(Throwable t) {
log.error(t);
}
#Override
public void onSuccess(FCMResponse response) {
log.info(response.toString());
}
});
The implementation for that is:
protected FCMResponse executeRequest(FCMRequest request) throws IOException {
byte[] content = this.objectMapper.writeValueAsBytes(request);
HttpURLConnection conn = this.connectionFactory.open(this.fcmUrl);
conn.setRequestMethod("POST");
conn.addRequestProperty("Authorization", getAuthorization(request));
conn.addRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(content.length);
LoggerFactory.getLogger("FCMDefaultAbstract").info("Authorization: " + conn.getRequestProperty("Authorization"));
LoggerFactory.getLogger("FCMDefaultAbstract").info("Content-Type: " + conn.getRequestProperty("Content-Type"));
LoggerFactory.getLogger("FCMDefaultAbstract").info("send: " + new String(content));
try (OutputStream outputStream = conn.getOutputStream()) {
IOUtils.write(content, outputStream);
} catch (Exception e) {
throw new FCMNetworkException("Error sending HTTP request to FCM", e);
}
FCMResponse response;
try (InputStream inputStream = conn.getInputStream()) {
response = this.objectMapper.readValue(IOUtils.toByteArray(inputStream), FCMResponse.class);
} catch (IOException e) {
try (InputStream inputStreamError = conn.getErrorStream()) {
String str = inputStreamError != null ? IOUtils.toString(inputStreamError) : "No error details provided";
int responseCode = conn.getResponseCode();
if (responseCode < 500) {
throw new FCMNetworkException(conn.getResponseCode(), str.trim(), e);
} else {
throw new FCMNetworkException(conn.getResponseCode(), str.trim(), checkForRetryInResponse(conn), e);
}
}
}
response.setRequest(request);
response.setRetryAfter(checkForRetryInResponse(conn));
Iterator<String> iteratorId = request.getRegistrationIds().iterator();
Iterator<FCMResult> iteratorResponse = response.getResults().iterator();
while (iteratorId.hasNext() && iteratorResponse.hasNext()) {
iteratorResponse.next().setRequestedRegistrationId(iteratorId.next());
}
if (iteratorId.hasNext()) {
LOG.warn("Protocol error: Less results than requested registation IDs");
}
if (iteratorResponse.hasNext()) {
LOG.warn("Protocol error: More results than requested registation IDs");
}
return response;
}
Here the log output:
FCMDefaultAbstract Authorization: null
FCMDefaultAbstract Content-Type:application/json
FCMDefaultAbstract send: {"registration_ids":["dMpvzp*************************************2lRsSl_5lFET2"],"data":{"CODE":"201","USER_ID":"1","ADDITION":"1468083549493"},"delay_while_idle":true}
FCM FCMNetworkException: HTTP 401: No error details provided
The Authorization header is not null in fact. it is correctly set with my FCM API Key. Only the HTTPUrlConnection implementation says to return null if someone trys to access Authorization key.
As you can see i am not able to connect with FCM. The Code 401 means that authentication failed.
What could be the problem here?
Check that you are using a server type API-KEY, and not a client or browser API-KEY.
If you are using Firebase you can find the API-KEY in
Project Settings > Cloud Messaging
If you are using cloud console, or you are not sure which key you are using,
you can generate a new key through through https://console.cloud.google.com
Quoting the documentation
https://firebase.google.com/docs/cloud-messaging/concept-options#credentials
Server key: A server key that authorizes your app server for access to
Google services, including sending messages via Firebase Cloud
Messaging. [...]
Important: Do not include the server key anywhere in your client code.
Also, make sure to use only server keys to authorize your app server.
Android, iOS, and browser keys are rejected by FCM.
When an Action is called and throws a specific exception, I use an ExceptionFilterAttribute that translate the error into a different response as HttpStatusCode.BadRequest. This has been working locally, but we pushed it to a server and now when I get the BadRequest I do not get any information in the reponse. What am I missing?
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
MyException ex = actionExecutedContext.Exception as MyException;
if (ex == null)
{
base.OnException(actionExecutedContext);
return;
}
IEnumerable<InfoItem> items = ex.Items.Select(i => new InfoItem
{
Property = i.PropertyName,
Message = i.ToString()
});
actionExecutedContext.Result = new HttpResponseMessage<IEnumerable<InfoItem>>(items, HttpStatusCode.BadRequest);
}
Edit: When I hit the service locally the body is included. It seems the problem is when hitting the service from a remote machine.
Try this:
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always
I am having trouble returning the correct HTTP error code for a "not found" in my WCF Web API code. Here is my api method ...
[WebInvoke(Method = "GET", UriTemplate = "{id}")]
[RequireAuthorisation]
public Customer GetCustomer(int id)
{
var customer = Repository.Find(id);
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return customer;
}
I also have a logging handler ...
protected override bool OnTryProvideResponse(Exception exception, ref HttpResponseMessage message)
{
if (exception != null)
{
var msg = "Request failed.";
_logger.Error(exception, msg);
}
message = new HttpResponseMessage
{
StatusCode = HttpStatusCode.InternalServerError
};
return true;
}
What is happening is I am getting the following exception ...
HttpResponseException
"The response message returned by the Response property of this exception should be immediately returned to the client. No further handling of the request message is required."
... which my logging handler picks up and changes the response status code to a 500.
So, based on reading a few blog posts and answers on SO, I changed to this ...
if (customer == null)
{
WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
return null;
}
... but this now give me a 200. Which is clearly wrong.
So, what is the right way to do this? It seems as if the throwing of the HttpResponseException doesn't work and the code after gets executed.
The code snippet for your error handler is always changing the response message to 500 no matter what as you are explicitly setting the status always to 500.
It sounds like what you are trying to do is return a 500 ONLY if it is an application error. If that is the case you should check if the error exception is an HttpResponseException and just return rather than overriding.
As to WebOperationContext, don't use it at all with Web Api as it is basically no-op.
Hope this helps
Glenn