I am making a Web Application. I have a WebAPI which my application connects with via HTTPS.
On the start there is a user logging - WebAPI logs in user after getting data from WebApplication and sends BearerToken and RefreshToken.
Next, my WebApplication stores the tokens. BearerToken is added to every request to WebAPI. After 15 minutes bearer expires. Now, application should get new bearer token using refresh token. I made it in very easy way. All my requests go through this method:
public async Task<HttpResponseMessage> SendAsyncRequest(string uri, string content, HttpMethod method, bool tryReauthorizeOn401 = true)
{
try
{
HttpRequestMessage rm = new HttpRequestMessage(method, uri);
if (!string.IsNullOrWhiteSpace(content))
rm.Content = new StringContent(content, Encoding.UTF8, ContentType);
HttpResponseMessage response = await httpClient.SendAsync(rm);
if (response.StatusCode == HttpStatusCode.Unauthorized && tryReauthorizeOn401)
{
httpClient.CancelPendingRequests();
bool res = await OnReauthorizeUser();
if (!res)
return response;
return await SendAsyncRequest(uri, content, method, false);
}
return response;
}catch(Exception ex)
{
Debug.Assert(false, ex.Message);
throw;
}
}
This should work like that:
I send a request
If I get 401 response and tryReauthorizeOn401 == true, I call OnReauthorizeUser event
In this event handler (which is not shown here) I eventually call again SendAsyncRequest with reauthorization data
I expected it to work. But it's not. It turns out that when I call SendAsynRequest with reauthorization data, I get TaskCancelletionException. Without any timeout. Just when hitting this: httpClient.SendAsync(rm); So my code "returns" to place after OnReauthorizeUser (previous call) with res == false.
So, I thought that maybe in some way I distort WebApplication http request lifecycle. I recon that during one request (for example - user clicks link with protected resource) I try send two requests:
get protected resource
reauthorize before main request ends
Am I right? How can I avoid such problems? What should I do to:
when I get 401 response, system should automatically refresh the tokens and call request one more time?
In other words:
user clicks "Show my documents" link
Application sends request to WebAPI: /documents/user/user-id
I get response from WebAPI with 401 error
Application then reauthorizes the user by sending request to WebAPI: /tokens/refresh/
I get response from WebAPI with new tokens (we assume that I will get this)
Application then sends again request from step two: /documents/user/user-id
So I would like to achieve this scenario.
User clicks in the link ONCE and then sees list of his documents. Without necessity to relogin (we assume that he is logged in, bearer token has expired in natural way and refresh token is valid)
Related
I have a Cookie based authentication in my webapp to which a sub application make some ajax requests to get the data from db.
the issue is that if the user is not authenticated i redirect him to expired.html, in test mode if i just run in browser or postman an api call like example.com/api/test without getting first the authentication cookie i'm correctly redirected to expired.html. the issue comes when i try to call that api via ajax so by making a simple .get request as the following:
function getPlu(codplu, callback){
let api = 'https://www.example.it/api/plu/?codplu=' + codplu
$.get( api, callback );
}
getPlu('COPERTI', callback => {
...
});
i just get the response from api with code 302 and a .get to expired.html with code 304 but the user still is not redirected to expired.html
So as you can see the status code for that api request is 302 and location should be expired.html BUT it's not getting redirected.
Might it be that browser doesn't handle automatically ajax redirects and i need to do it via client-side (redirect if status.code == 302) or i could fix it via server side?
Here is how the authentication makes the redirect
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "AUTH_TOKEN";
options.Cookie.MaxAge = TimeSpan.FromMinutes(120);
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (context) =>
{
context.HttpContext.Response.Redirect("https://www.example.it/vmenu/expired.html");
return Task.CompletedTask;
}
};
});
Just to make this answer more clear:
jQuery's ajax uses the XMLHttpRequest object and its methods to execute requests. XMLHttpRequest will follow redirects automatically. Since it's XMLHttpRequest who does that, jQuery's ajax function doesn't even know about it. It only receives the final response, which in the OP's case is 200 Ok (or 304 Not Modified as OP posted).
Also, since the request is made by jQuery/XMLHttpRequest, the client view is not changed if a request or a redirect is executed. Everything is only in the browser's "behind execution".
Since all redirects are executed automatically by XMLHttpRequest, and jQuery is not able to tell if a redirect was made, the most reliable way (and that's the most important thing to me) is handle it manually:
1 - On server side, when unauthenticated request, add a custom header to the response, and respond with 200 OK:
OnRedirectToLogin = (context) =>
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
context.Response.Headers.Add("X-Unauthenticated-Redirect", "https://www.example.it/vmenu/expired.html");
return Task.CompletedTask;
}
2 - On client side, just check if this custom header exists. If it does, redirect manually using window.location:
var redirectHeader = jqXHR.getResponseHeader('X-Unauthenticated-Redirect');
if (redirectHeader.length > 0) {
window.location = redirectHeader;
}
Just for reference, from XMLHttpRequest docs:
If the origin of the URL conveyed by the Location header is same origin with the XMLHttpRequest origin and the redirect does not violate infinite loop precautions, transparently follow the redirect while observing the same-origin request event rules.
I recently changed from windows authentication to Azure AD using roughly the "out of the box" code;
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
//AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationContext authContext = new AuthenticationContext(Authority);
return authContext.AcquireTokenByAuthorizationCodeAsync(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
}
}
});
}
Our users have started to get intermittent 404 errors when trying to submit certain forms. I think I have managed to recreate the issue by deleting cookies, so I suspect it's tied to when the session naturally times out.
If I look at the flow with a HTTP GET request it looks like;
HTTP GET https://myappurl/page?param1=value¶m2=value
HTTP 302 response with redirect to https://login.microsoftonline.com (including various params; state, client_id etc)
HTTP 200 response (not quite sure how/why it then knows to redirect)
HTTP GET https://myappurl/
HTTP 302 response with redirect to original URL https://myappurl/page?param1=value¶m2=value
HTTP GET https://myappurl/page?param1=value¶m2=value
HTTP 200 response
Everything works a treat...
For a HTTP POST however;
HTTP POST to https://myappurl/another_page
HTTP 302 response with redirect to https://login.microsoftonline.com (including various params; state, client_id etc)
HTTP 200 response (not quite sure how/why it then knows to redirect)
HTTP GET https://myappurl/
HTTP 302 response with redirect to original URL https://myappurl/another_page
HTTP GET https://myappurl/another_page
HTTP 404 response
Fails because the endpoint only accepts HTTP POST requests.
Any idea if/how I can fix this? I would have thought the built in state tracking or whatever it is doing would store the original request and continue where it left off regardless...
It looks like you are not using the token cache. What this means is that a user's session will expire after about an hour after they sign into the application.
To address this issue you should use AcquireTokenSilentAsync whenever the application needs an access token. This method will automatically refresh the token for you using it's In Memory cache. For more details see https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-a-cached-token
I know that I can return a custom reponse when using a custom authentication provider like the code below:
Return a custom auth response object from ServiceStack authentication
I'm just wondering if there is a way to return a custom HTTP response code.
For example, when the authentication fails, instead of a 401 unauthorized error, I want to send another HTTP response code to give more details on what failed. For example, when the account got locked, I will send the error code XYZ!
public class MyBasicAuthProvider : BasicAuthProvider
{
public override object Authenticate(ServiceStack.ServiceInterface.IServiceBase authService, IAuthSession session, Auth request)
{
//let normal authentication happen
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
//return your own class, but take neccessary data from AuthResponse
return new
{
UserName = authResponse.UserName,
SessionId = authResponse.SessionId,
ReferrerUrl = authResponse.ReferrerUrl,
SessionExpires = DateTime.Now
};
}
}
In a try catch, I found a way of return a custom HTTP code in that function. I return for example:
return new ServiceStack.HttpError(423, "Locked");
I'm not sure if this is the right way
The BasicAuthProvider is an IAuthWithRequest Auth Provider that enables HTTP Basic Auth where it authenticates when calling a Service, i.e. it does not authenticate using an explicit request to ServiceStack's /auth endpoint.
For failed Basic Auth requests you want to return ServiceStack's 401 Unauthorized Status response with the WWW-Authenticate HTTP Header which is required for HTTP Clients to know to prompt for credentials.
I'd recommend against using a different Error Response, but if you really want to customize the Failed Response for HTTP Basic Auth requests you can override OnFailedAuthentication in your BasicAuthProvider to write the custom Error Response you want:
public virtual Task OnFailedAuthentication(IAuthSession session, IRequest httpReq, IResponse httpRes)
{
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
httpRes.AddHeader(HttpHeaders.WwwAuthenticate, "{0} realm=\"{1}\"".Fmt(this.Provider, this.AuthRealm));
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
}
I am building my URL to make an API call, using the key and secret that the provider has given me.
https://api.testurl.com/api/test/calldata?key=12345&secret=999999&query=hello
My question is I am appending the 'query' based on user input each time and performing the call with the 'key' and 'secret' every time - to me this doesn't seem that secure. Isn't the secret key exposed each time the call is made?
public async Task<List<APIResult.Data>> ApiAsync()
{
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(_apiUrlToCall);
if (!response.IsSuccessStatusCode) return null;
var result = await response.Content.ReadAsStringAsync();
var rootResult = JsonConvert.DeserializeObject<APIResult.Rootobject>
(result);
return rootResult.Data.ToList();
}
}
Normally you'd pass the identity data (in this case your key and secret) in a HTTP header rather than on the querystring. That way it doesn't get logged anywhere (e.g. IIS logs, browser history, slurped by google, facebook et al trackers).
If you're using HTTPS that should stop it being exposed anywhere else.
But yes since HTTP is stateless you have to send some sort of identifying data every time you make a request, be that a secret key, Kerberos token, session coookie, whatever it is.
You can pass the key & secret as Http header. Normally for rest api the Authorization Http Header is set with the authtoken. You could so something similar.
I am new to Token Based authentication. With reference to below links, I am trying to understand Token Based authentication.
http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
If the user credentials are valid, I am getting the desired token.
[AcceptVerbs("POST")]
[HttpPost]
public string Post([FromBody]User user)
{
if(user.Username == "hello" && user.Password == "123")
{
var accessTokenResponse = GenerateLocalAccessTokenResponse(user.Username);
return accessTokenResponse.ToString();
}
else
{
return "User invalid";
}
}
Generated token
TWC1Q2rrenZC2p78KPnS4JblcepCg6q3XuxqBQIh7L003npbb6hlBAOYGRN03OvY_O55GWFkZp7UfCmhCgH9Z4rBsjvIrp8gyCp4HmxpP4axVKk10NM9fiG2ctgZWeSbw1jNOor42Wk3yMufbs4xP0RlNuvdpLtBLir52g9rPF053kiJtYryNGzPsbibXHRrNoy0wOR2384uLAJ5pNE9s1DwYpdPKB9uOLSAGhDQOVU,
Now when I try to access the secured resources
[Authorize]
[HttpGet]
// GET api/orders/5
public string Get()
{
return "This is a secure resource";
}
I get "Access Denied Error".
How do I use the token to access such resources.
Any help/suggestion highly appreciated.
Thanks.
usually you would not implement the token endpoint as a POST method in your controller, but create a separate class (SimpleAuthorizationServerProvide) for it as shown in the above mentioned tutorial.
If everything is setup correctly, you have to add an Authorization header to your http request
Authorization: Bearer TWC1Q2rrenZC2p78KP...
and get a reply with status code 200(OK)
To get a token send a request (for example with the tool fiddler) to your token endpoint
e.g. if your service is running on localhost on port 52180 it looks like this:
POST http://localhost:52180/token
grant_type=password&username=admin&password=123&client_id=abc
the grant_type part is the request body.
When you post the above request, you'll reach the token endpoint. Just as Taiseer wrote in Step 12 of the tutorial.
When you put a breakpoint at GrantResourceOwnerCredentials that should be reached as soon as you sent the above request.
The usual flow is:
- client requests a token from http://localhost:52180/token
server authenticates user credentials in GrantResourceOwnerCredentials and issues a token
client reads the access_token from the token response
client adds an authorization header containing the access_token to a request
http://localhost:52180/api/orders
Authorization: Bearer TWC1Q2rrenZC2p78KP...
server reads Authorization header and grants access (if token is valid)
server processes request, eg, the GET request
client receives status 200 and desired data
The api controller shown above looks ok
The [Authorize] attribute is all you need in your controller. That adds an AuthorizationFilter to the http request pipeline, which handles the authorization for you when the client adds the above mentioned Authoriztion header to the request.