.NET Web API 2 OWIN Bearer Token Authentication direct call - validation

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.

Related

Best way to add and retrieve new post parameters to the GrantRefreshToken() in OWIN Web API

the default request parameters to get new JWT using refresh token are:
grant_type , refresh_token and client_id .
I need to control the claims identity modification by adding new body parameter when requesting a new refresh token.
let say the parameter is named by grant_claims, which can hold true or false boolean value.
how can I get that custom parameter in the GrantRefreshToken() overridden method?
Many Thanks
Finally, I found the answer from this post :
owin oauth send additional parameters
in the ValidateClientAuthentication we can add additional params
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// other code ...
var grantClaims = context.Parameters.Get("grant_claims");
// other code ...
context.OwinContext.Set<string>("grant_claims", grantClaims);
// other code ...
}
then get the values in the authentication and refresh token methods
// auth
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var grantClaims = context.OwinContext.Get<string>("grant_claims");
}
//refresh token
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var grantClaims = context.OwinContext.Get<string>("grant_claims");
}

websockets and authentication with identityserver4

I am using .net core 1.1 and identityserver 4 to get tokens and validate users. The web api works fine reading the bearer token from the headers and getting the user principal claims.
Now I want to use a websocket (not SignalR) for sending notifications. I can open a ws:// channel (or wss) but token isn't sent with the headers, so in the .net core application I have no information of the user (User Claims and Identity).
How can I authenticate the user through the websocket? I did a search but couldn't find any helpful information.
Thanks
There are two main problems related to the authentication in WebSocket middleware:
Authorization should be called manually
First of all, authorization is not applied to web socket request (as it is not a controller which can be marked with Authorize attribute).
That's why in WebSocket middleware you need to call authorization by your self. This is easy to achieve by calling AuthenticateAsync extension method of the HttpContext object.
So, your middleware will be look something like this:
public class WebSocketMiddleware
{
private readonly RequestDelegate next;
public WebSocketMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
AuthenticateResult authenticateResult =
await context.AuthenticateAsync(OAuthValidationDefaults.AuthenticationScheme);
....
});
}
So, using authentication results you can check if the user is authenticated or not and then access authenticated user information.
Passing bearer token to web sockets request
For Web Socket connections, the default Authorization header does not work, because the WebSockets JS API doesn't allow setting custom parameters. To work around this limitation, the access token is passed quite often in the query string.
To make authentication middleware to use it, you need to update authentication validation options. This basically can be done in your startup script like this:
services
.AddAuthentication()
.AddOAuthValidation(options =>
{
options.Events = new OAuthValidationEvents
{
// Note: for Web Socket connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
The following code can be used as an example to add access token to web socket url during connection initializing:
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const wsUri = protocol + "//" + window.location.host + "/ws" + "?access_token=" + token;
this.socket = new WebSocket(wsUri);

How to use AddExternalLogin method from WebAPI template

I use Web API template. My target is REST service with Facebook, Twitter, and Google registration and authorization.
I can create account using social networks, but can't add another social network login to existing account.
Specifically, the problem is occurring in this method:
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
And even more specifically, the value returned from this call is null,
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
Which explicitly causes the API to return BadRequest("External login failure.");.
The only member in AddExternalLoginBindingModel is ExternalAccessToken, which I'm filling with a social network access token.
How do I add an external login to an existing account? What information should I send to the AddExternalLogin method?
Don't know if I'm doing something conceptually wrong, but I just solved this by avoiding using AccessTokenFormat;
Basically is the same flow as when you login at the first time. But when you get the auth token, then you must set Authorization header ("Bearer " +) and call the adjusted api bellow:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin() {
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null) {
return InternalServerError();
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(info.Login.LoginProvider, info.Login.ProviderKey));
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
if (!result.Succeeded) {
return GetErrorResult(result);
}
return Ok();
}

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

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.

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