ASP.NET Web API - Passing a UserToken(string) to a LoginController using a DelegatingHandler - delegates

I have an ASP.NET Web API project and on initial user login, the username and password are sent in an http header over SSL and validated by the server.
The server creates a database record with the UserId, a randmon 64 character string (UserToken), expiration date and the client IP address.
The UserToken is then sent back to the client and then be stored in a cookie.
All subsequent requests send the UserToken in an http header and that is validated using the calling IP address by the server.
This way, the username and password are only sent once, and all calls using the UserToken are logged.
I have created two custom DelegatingHandlers - LoginAuthenticationHandler, and TokenAuthenticationHandler - which process the http headers and send an appropriate 200 or 400 http response.
////////////////
Seems my only problem is that I want the LoginAuthenticationHandler to also return the UserToken to the client, so it can store the cookie.
Sorry for the verbosity :-\
Also - I'm new to Web API - so maybe this is not the best place for this to be done - but it would be very convenient if the UserToken can be passed back to the LoginController in this way.
Thanks for any input :-)
Some related SO posts:
DelegatingHandler for response in WebApi
Is it possible to pass data from DelegatingHandler to Controller in ASP.NET Web API?
////////////////
public class LoginAuthenticationHandler : DelegatingHandler
{
public const string BasicScheme = "Basic";
public const string ChallengeAuthenticationHeaderName = "WWW-Authenticate";
public const char AuthorizationHeaderSeparator = ':';
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Get Authorization Http Header
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Check if Basic Authentication
if (authHeader.Scheme != BasicScheme)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Decode UserName + Password from Http Header
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
if (credentialParts.Length != 2)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
var username = credentialParts[0].Trim();
var password = credentialParts[1].Trim();
// Authenticate Username + Password and Return UserToken
var userId = new Users().GetUserIdFromUserNamePassword(username, password);
if (userId == 0)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// User is Authorized - Create New UserToken
var ipAddress = HttpContext.Current.Request.UserHostAddress;
var userToken = new Users().CreateUserToken(ipAddress, userId);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
//======================================================
// Return UserToken to Login Controller to be Stored as Cookie on the Client
// response.Content = userToken ??
// maybe set header for userToken ??
// HttpRequestMessage Properties ??
return response;
//======================================================
});
}
private static Task<HttpResponseMessage> CreateUnauthorizedResponse()
{
// Send Back Http Unauthorized if Authentication Fails
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add(ChallengeAuthenticationHeaderName, BasicScheme);
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
taskCompletionSource.SetResult(response);
return taskCompletionSource.Task;
}
}
}

Generally, HTTP services are stateless and the concept of login does not apply. LoginController is for the MVC controllers and not web API. What you are trying to do is not a good practice, even though it is technically possible to achieve.
If you really want to do what you are trying to do, do not think along the lines of sending the session data (what you call the user token) to LoginController. You can write the cookie into the response from your message handler itself. See this. You must only store encrypted data into a cookie in that case. Instead of creating your own cookie and all that, you can use Forms Authentication and create a cookie with FA ticket. See this.
BTW, it is possible and easy to spoof client IP addresses.

Perhaps you could login using the controller without using a DelegatingHandler: you could return the token to the client to be added to the header of future API calls, or add it to the header in the controller using the Request.Headers.Add function.
Then you would not need two custom DelegatingHandlers, the TokenAuthenticationHandler would be sufficient. But you would want to specify that all requests other than the initial login are funneled through the TokenAuthenticationHandler.
To do that, you will need to customize the WebAPI routes. In the default Web API projects, this is currently done in the WebApiConfig.Register method in WebApiConfig.cs (called from Global.asax.cs). First, have all your API calls route through your TokenAuthenticationHandler; then add the login route plainly such that it does not funnel through your TokenAuthenticationHandler:
//this message handler chain is used to put TokenAuthenticationHandleron all API requests and not Login
DelegatingHandler[] handlers = new DelegatingHandler[] {
new TokenAuthenticationHandler()
};
var routeHandlers = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handlers);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: null,
constraints: null,
handler: routeHandlers
);
//login route
config.Routes.MapHttpRoute(
name: "Login",
routeTemplate: "login/{action}",
defaults: new { Controller = "Login" }
);
Now, you can validate the token in the TokenAuthenticationHandler using request.Headers.TryGetValues to get it:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
//token validation
IEnumerable<string> foundValues = null;
if (request.Headers.TryGetValues("AuthenticationToken", out foundValues))
{
if (foundValues.Count() == 1)
{
string token = foundValues.Single();
AuthenticationDAO dao = new AuthenticationDAO();
if (dao.AuthenticateUser(token))
{
//add values to request.Properties for use in Web API controllers
request.Properties.Add(new KeyValuePair<string, object>("SomeValue", 4));
//Engage!
return base.SendAsync(request, cancellationToken);
}
}
}
//fail if token not present or not valid
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetResult(new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("Missing or invalid authorization token.")
});
return tcs.Task;
}
As per your original question of passing values from the DelegatingHandler to the Controller, that is easily possible using the request.Properties.Add function as demonstrated above.
Some additional considerations:
I am not sure that sending the login credentials in the header is any
more secure than just as content in the request, since it is all over
SSL.
You should consider implementing an AntiForgeryToken. This
article is a good starter, and this SO post points out how
you could use DelegatingHandler to also only check for it on web
requests (allowing your api to be accessed from native apps).
You can easily add a DelegatingHandler that applies to all requests
that enforces HTTPS.
Hope that helps. What I've outlined is the way I'm doing it, so I hope for some comments if it's wrong.

Related

Do I need a second HttpClient to make an API call to refresh JWT token inside custom DelegatingHandler?

For each API call in my App, I want to check whether the user has an expired JWT, and if so, I want to get a new one using a refresh token, and then proceed with the original request to API. This is supposed to all work in the background without the APP user experiencing any interruptions or need to login again.
I create my HttpClient like this:
static DelegatingHandler handler = new AuthenticationHandler();
static HttpClient httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://10.0.2.2:5001/api/v1")
};
AuthenticationHandler is a custom DelegatingHandler which has an override SendAsync method. Inside that method I check if request has status Unauthorised:
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
And if it does, I need to send another request to my API with the currently owned JWT and Refresh tokens to generate new pair of tokens... Since this is an API call in the middle of another API call (as it all happens inside the custom DelegatingHandler which is a parameter for constructing my main HttpClient) - does refreshing the token needs to happen using a second HttpClient that I need to create literally to make the refresh token call?
I can't see how can I use the same HttpClient for this, how is this usually being done?
EDIT:
I can't see how I could use the same HttpClient for refreshToken call from inside AuthenticationHandler, as the handler is used to construct the HttpClient. Feels like a circular reference. I just have no idea how others do it in their code... I currently implemented it by using that second HttpClient which I only use for that one refreshToken call, and it works, but I have a feeling that there is a cleaner way to achieve this?
Btw, my (not refactored yet) SendAsync method inside AuthenticationHandler looks like this currently:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = new HttpResponseMessage();
request = CheckForAuthToken(request);
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
for (int i = 1; i == _maxRefreshAttempts; i++)
{
// Here I make a call to the API to refresh and return a new JWT, The authApiService uses a different HttpClient
RefreshTokenRequestModel refreshTokenRequestModel = new RefreshTokenRequestModel
{
Token = await SecureStorage.GetAsync("jwtToken"),
RefreshToken = await SecureStorage.GetAsync("refreshToken")
};
var apiResponse = await authApiService.RefreshToken(refreshTokenRequestModel);
if (apiResponse.IsSuccessStatusCode)
{
await SecureStorage.SetAsync("jwtToken", apiResponse.Content.Token);
await SecureStorage.SetAsync("refreshToken", apiResponse.Content.RefreshToken);
request = CheckForAuthToken(request);
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
}
return response;
}
catch (Exception e)
{
throw e;
}
}

Web API - Get information encrypted inside token, ticket ExpiresUtc and IssuedUtc

I am using Web API as my back-end and implemented the token security using the built in mechanism. In the template code, when issuing the access token, I can get the issued and expired dates of the token:
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
var issued = context.Properties.IssuedUtc;
var expired = context.Properties.ExpiresUtc;
.
.
.
}
Now when a request is made to a method that requires authorization I want to do something similar:
[Authorize]
public async Task<string> GetTokenInfo()
{
//var issued = GetCurrentTicket().Properties.ExpiresUtc;
//var issued = GetCurrentTicket().Properties.IssuedUtc;
.
.
.
}
So how can I get the information encrypted inside the token, more specifically the ExpireUtc and IssuedUtc ?
You can easily retrieve the AuthenticationProperties dictionary using IAuthenticationManager.AuthenticateAsync, which returns a AuthenticateResult object: https://msdn.microsoft.com/en-us/library/dn270674(v=vs.113).aspx
From a Web API controller, you'll need the GetOwinContext extension to get the OWIN context from the request message and use IOwinContext.Authentication: https://msdn.microsoft.com/en-us/library/system.net.http.owinhttprequestmessageextensions.getowincontext(v=vs.118).aspx
var context = Request.GetOwinContext();
var result = await context.Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
if (result == null) {
throw new InvalidOperationException();
}
var properties = result.Properties;
(of course, you also need to have a properly configured app.UseOAuthBearerAuthentication call in your Startup class, but I assume it's the case here).

How to customize the System.Web.Http.AuthorizeAttribute with Microsoft.Owin.Security?

I've implemented a custom AuthorizeAttribute in my WebAPI (note that this is different from the MVC AuthorizeAttribute).
I've overridden the OnAuthorization method. In this method I check if the user is authenticated. If not authenticated, I challenge the user to login.
Part of my custom logic is to check authenticated users if they are authorized to continue (basically I check their name/email. if it exists in a predefined list, then they have access).
The issue I see is this:
After the user successfully authenticates BUT FAILS to be authorized, I see that there is an infinite loop redirection to the login page.
Again, the challenege for user credentials is in the OnAuthorization method.
What might be causing this infinite looping, and how to prevent this once user has been determined to have no authorization?
* Updated with snippet *
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
base.OnAuthorization(actionContext); // Should this be here?
var owinContext = HttpContext.Current.GetOwinContext();
var authenticated = owinContext.Authentication.User.Identity.IsAuthenticated;
var request = System.Web.HttpContext.Current.Request;
if (!authenticated)
{
// Challenge user for crednetials
if (!request.IsAuthenticated)
{
// This is where the user is requested to login.
owinContext.Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
WsFederationAuthenticationDefaults.AuthenticationType);
}
}
else
{
// At this point the user ia authenticated.
// Now lets check if user is authorized for this application.
var isAuthorized = SecurityHelper.IsUserAuthorized();
if (isAuthorized)
{
// authorized.
return;
}
// not authorized.
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
You could try removing OnAuthorization and adding this:
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var owinContext = HttpContext.Current.GetOwinContext();
var authenticated = owinContext.Authentication.User.Identity.IsAuthenticated;
return authenticated & SecurityHelper.IsUserAuthorized();
}
I don't get why you're redirecting on failed authentication, surely an API should just return 401?
I'm wondering about this bit of code right here:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
Somewhere you must be configuring your OWIN layer using something like the following:
var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString(loginPath)
}
app.UseCookieAuthentication(cookieAuthenticationOptions);
When you return a 401 from the authentication filter the OWIN infrastructure is automatically going to redirect you to whatever LoginPath you specified. But when trying to satisfy that request it's invoking your filter, but because the user isn't authorized it returns a 401 which causes a redirect to the LoginPath, and so on, and so on.
Because this is an API call you need to handle the 401 differently. The following blog post talks about this situation.
http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
In a nutshell, when configuring your CookieAuthenticationOptions you need to specify your own Provider and only direct if it's not an AJAX request.
var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString(loginPath),
Provider = new CookieAuthenticationProvider()
{
OnApplyRedirect = context =>
{
if (!context.Request.IsAjaxRequest())
{ context.Response.Redirect(context.RedirectUri); }
}
}
}

.NET Web API 2 OWIN Bearer Token Authentication direct call

I have a problem with my Web Api Project.
I have files stored in my Database and want to call them directly in a new window to view/save (URL like : /api/Files/5 - 5 beeing the FileId)
I got everthing working with the Bearer Token for my general AJAX requests with AngularJS for normal Data and it works like a charm. For the file I created a Controller that shows the file in the browser with the corresponding MIME-Type. But now that I changed the action to [Authorize] I get an Access Denied which is correct because I didnt pass an access_token in the HTTP-Header.
I did quite some research if it is possible to pass the Token via the querystring but didn't find anything helpful.
Now my plan is to remove the [Authorize] Attribute from my Controller and try to validate the token myself but I don't know how.
Anyone know how I can get it to work?
I implemented bearer token authentication in my app (AngularJS, WebAPI 2) and I had similar problem - I needed to allow downloading files by clicking on a link. When you click on a link headers are not sent. :(
So, I sent the token value in a query string to download a file
.../mywebapp/api/files/getfile/3?access_token=jaCOTrGsaak6Sk0CpPc1...
and set "Authorization" header to the token value in Startup.Auth.cs. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers.Get("Authorization")))
{
var queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
string token = queryString.Get("access_token");
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
}
}
}
await next.Invoke();
});
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
This feature is already built in - I wrote about it here:
http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/
For ASP .Net Core I did something like this based on Forward's answer
Extension Method
public static void UseQueryStringBearerValidation(this IApplicationBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"].ToString()))
{
var queryString = QueryHelpers.ParseQuery(context.Request.QueryString.Value);
var token = queryString["access_token"].ToString();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] {$"Bearer {token}"});
}
}
}
await next();
});
}
Usage
StartUp.cs -> Configure() method
app.UseCustomExceptionHandler();
app.UseQueryStringBearerValidation(); // <-- add before Jwt Handler
app.UseCustomJwtBearerValidation();
app.AddHttpContextProperties();
app.UseStaticFiles();
app.UseMvc(MiddlewareAppConfiguration.AddRouteMappings);
Although I'm not sure it's a very good idea, you could implementing a DelegatingHandler to achieve what you are looking for.
public class QueryStringBearerToken : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var bearerToken = request.GetQueryNameValuePairs()
.Where(kvp => kvp.Key == "bearerToken")
.Select(kvp => kvp.Value)
.FirstOrDefault();
if(!String.IsNullOrEmpty(bearerToken))
{
request.Headers.Add("Authorization", "Bearer " + bearerToken);
}
return base.SendAsync(request, cancellationToken);
}
}
This handler will look for the query string named "bearerToken" and, if it exists, will add it to the request header for the subsequent handlers / filter to process. You might want to check first if the header is already present and not override in this case. You can add this handler in your configuration phase in the usual fashion:
config.MessageHandlers.Insert(0, new QueryStringBearerToken ());
A request for /YourRoute?bearerToken=theToken will pass in the DelegatingHandler, adding the token passed in the query string to the list of headers in the original request and the regular Bearer Token authentication will look for the header and find it.

WebApi authorization filter with token in json payload

I've been looking into Authorization with AspNetWebApi and information is a little sparse on the subject.
I've got the following options:
Pass API token on query string
Pass API token as header
Pass API token using Basic Auth
Pass API token onto the request payload in json.
Which is generally the recommended method?
I'm also wondering for point 4), how would I go about inspecting the json payload in the OnAuthorization method on the AuthorizationFilterAttribute to check whether the API token is correct?
If you want a truly secure option for authorization, something like OAuth is the way to go. This blog post provides a pretty thorough sample using the now obsolete WCF Web API but a lot of the code is salvageable. Or at least, go with using HTTP basic authentication as shown in this blog post. As Aliostad notes, make sure you're using HTTPS if you go the Basic authentication route so the token stays secure.
If you decide you want to roll your own (which almost always will be much less secure than either option above) then below is a code sample of what you'll need for the AuthorizationHanlder if you go HTTP header route. Be aware there's a good chance the way the UserPrinicipal is handled in Web API classes may change so this code is only good for the first preview release. You would need to wire-in the AuthorizationHandler like this:
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthenticationHandler());
Code for header token:
public class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestAuthTokenList = GetRequestAuthTokens(request);
if (ValidAuthorization(requestAuthTokenList))
{
//TODO: implement a Prinicipal generator that works for you
var principalHelper = GlobalConfiguration.Configuration
.ServiceResolver
.GetService(typeof(IPrincipalHelper)) as IPrincipalHelper;
request.Properties[HttpPropertyKeys.UserPrincipalKey] =
principalHelper.GetPrinicipal(request);
return base.SendAsync(request, cancellationToken);
}
/*
** This will make the whole API protected by the API token.
** To only protect parts of the API then mark controllers/methods
** with the Authorize attribute and always return this:
**
** return base.SendAsync(request, cancellationToken);
*/
return Task<HttpResponseMessage>.Factory.StartNew(
() => new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("Authorization failed")
});
}
private static bool ValidAuthorization(IEnumerable<string> requestAuthTokens)
{
//TODO: get your API from config or however makes sense for you
var apiAuthorizationToken = "good token";
var authorized = requestAuthTokens.Contains(apiAuthorizationToken);
return authorized;
}
private static IEnumerable<string> GetRequestAuthTokens(HttpRequestMessage request)
{
IEnumerable<string> requestAuthTokens;
if (!request.Headers.TryGetValues("SomeHeaderApiKey", out requestAuthTokens))
{
//Initialize list to contain a single not found token:
requestAuthTokens = new[] {"No API token found"};
}
return requestAuthTokens;
}
}

Resources