How can I validate a received token in Asp.NET WebAPI? - asp.net-web-api

My project contains an ASP.NET MVC application (with no authentication) and two Web APIs.
The first API (Account API) is built on top of Owin middleware, using ASP.NET identity, in order to have a token-based authentication for ASP.NET MVC.
This works very fine for ASP.NET MVC.
But the second API is a data handler API (Data API).
The problem is that I need to authorize the incoming requests on Data API. From the ASP.NET MVC application, I have the following request.
Start Edit 1
// The MVC controller makes a request in order to authenticate the user.
// If the authentication succeeds, an access token is returned.
var authorizationUrl = "my.authorization.api";
HttpResponseMessage result = httpClient.PostAsync(authorizationUrl, content).Result;
// In the Home controller I have these lines of code.
var user = ((ClaimsPrincipal) HttpContext.Current.User);
var token = x.Claims.First(t => t.Type == "AccessToken").Value;
// Now I just need to call the Data API.
var dataUrl = "my.data.api/home";
var request = WebRequest.Create(dataUrl) as HttpWebRequest;
request.Method = WebRequestMethods.Http.Get;
request.ContentType = "application/json";
request.Headers.Add("Authorization", "Bearer " + accessToken);
var response = (HttpWebResponse) request.GetResponse();
The data API has an [Authorize] attribute on HomeController and I receive 401 Unauthorized every time I call any method.
End Edit 1
The questions is: how can I validate the incoming request (on Data API) using the accessToken from request's header (sent from MVC application)?
Thank you!

Related

Subdomain webapi (asp.net mvc) responds on direct requests, but not from requests at the same domain

WebSite : domain.com
Api : api.domain.com
If I make a direct request to localhost api, it works correctly.
If I make a direct request to hosted api, it works correctly.
But if I make a request from a web site on the same domain, it does not work.
When I make a request from web site to another hosted domain with the same api, it also works.
I also tried from a website with a javascript request. First I didn't work, but after adding <add name="Access-Control-Allow-Origin" value="*" />, it does work.
It is clear that the problem is related to the subdomain. How can I do a same domain website request to get a response from the controller? It looks like it has to do with routing..
Thanks
Thanks for advices..
I try to set CORS settings. I follow this doc (https://learn.microsoft.com/tr-tr/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api)
I forget to say my first post. Website (https://domain.com.tr) works on https but api (http://api.domain.com.tr) works on http
These steps are applied
config.EnableCors(); added webapi project
[EnableCors(origins: "https://domain.com.tr", headers: "*", methods: "*")] added top of the ValuesController : ApiController in webapi project
Then i make a request from other project https://domain.com.tr but it fails again which is my code below
/***NOT WORK***/
string apiUrl = "http://api.domain.com.tr/api/";
/***WORK PROPERLY***/
//string apiUrl = "http://otherdomain.com/api/";
/***WORK PROPERLY***/
//string apiUrl = "http://localhost:57232/api/";
IEnumerable<Modul> students = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
//HTTP GET
var responseTask = client.GetAsync("GetPages");
responseTask.Wait();
/***AFTER THİS LINE GIVE ERROR***/
var result = responseTask.Result;
enter image description here

How to best implement simple validation of JWT credential to data being passed to my controller

My ASPNetCore web API is using JWT (Json Web Tokens) for authentication. The JWT token has an external and internal user ID inside it. Having these ID's in the JWT does not concern me, as JWT's can't be tampered with or they become invalid, and the internal ID is not useful anywhere outside the system. Of course, the password is not in the JWT content.
Within the JWT, the external user ID becomes the user's System.Security.Claims.ClaimType.Name. The internal ID is set as a JwtRegisteredClaimName.UniqueName value.
When calls are made to the web API, it is good that the [Authorize] attribute attribute makes sure that the user has authenticated and has a currently valid JWT. The concern I have is that once the user is logged in, there is an opportunity for hacking by using the Web API, sending external or internal user id's as criteria that do not match the currently authenticated user. Some web methods in the controllers accept the internal user ID as part of the request being posted, for example, a call to save user information has the internal user ID inside, used as the key for saving the data. I need to be sure that the authenticated user matches/is the same as the user whose data is being saved via the Web API.
My Question is how and where to best implement this data-level security in my web api? Policies don't seem like they can be applied against the data being passed. Authorization filters don't seem to have access to the message body nor any data bindings. Action filters (Microsoft.ASPNetCore.MVC.Filters) run later, but seem like they may not really be intended for this. Also, how do you access the body of the message that was posted inside an action filter? Or should I always make sure that the user ID is passed to methods as a consistently named parameter that I can access via ActionExecutingContext.ActionArguments?
I've searched many posts and not found any scenarios that match what I'm trying to do.
You can always use Middleware to intercept the call when the Response object has been populated , see code sample form here and here .
Authorization filters could also read the request body with EnableRewind :
public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var request = context.HttpContext.Request;
context.HttpContext.Request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
var requestBody = stream.ReadToEnd();
}
}
}
Also works in action filters :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ReadableBodyStreamAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var request = actionContext.HttpContext.Request;
var route = request.Path.HasValue ? request.Path.Value : "";
var requestHeader = request.Headers.Aggregate("", (current, header) => current + $"{header.Key}: {header.Value}{Environment.NewLine}");
var requestBody = "";
request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
requestBody = stream.ReadToEnd();
}
if (...)
{
var wrongResult = new { error = "Wrong parameters" };
actionContext.Result = new JsonResult(wrongResult);
}
}
}

Authentication for both ASP.NET Core 2 MVC and WebAPI in the same webapp

Today, I have a ASP.NET Core 2 web app which contains standard cookie-based authentication for use with the MVC frontend with razor views, and also a WebAPI with JWT for use via other front-ends.
With this, the same user can log in via a MVC razor webpage and via a WebAPI call and access the protected actions for each. These two parts are working fine, but independent of one another.
What I would like to do now is to extend parts of my MVC front-end to use VueJS and call my WebAPI behind the scenes.
When a user logs in via the MVC webapp, the ASP.NET Core 2 middleware creates the cookie and provides in back to the user. But when I want to make requests via VueJS into my WebAPI I need to also have a JWT to pass in the header.
The part I'm struggling with is how to get a JWT when the user logs in via the MVC webpage in addition to the cookie. In the MVC login action, I of course can generate the JWT, but how do I get this back to the user and store it in localStorage?
Here is my current Login action in my MVC controller (very similar to template):
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var userToVerify = await _userManager.FindByEmailAsync(model.Email);
var identity = await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(model.Email, userToVerify.Id));
// At this point 'jwt' contains the complete token object I would need to store in localStorage
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, model.Email, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}

CSRF for Ajax request across applications in one Domain

We have MVC 4 application which is hosted on Web Farm. Site has 5 applications hosted under one domain. These applications communicate with each other. We are implementing Cross Site Request Forgery for our application. We have added AntiForgeyToken(#Html.AntiForgeryToken()) on Layout page. When we try to post data actions across applications using Ajax request, we are facing below exception-
Exception:
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
For Ajax request we have added “__RequestVerificationToken” value into prefilter as shown below-
Client side implementation:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.type.toLowerCase() == "post") {
if ($('input[name="__RequestVerificationToken"]').length > 0)
jqXHR.setRequestHeader('__RequestVerificationToken',
$('input[name="__RequestVerificationToken"]').val());
}});
On Server side we validated this token as shown below-
string cookie = "";
Dictionary<string, object> cookieCollection = new Dictionary<string, object>();
foreach (var key in HttpContext.Current.Request.Cookies.AllKeys)
{
cookieCollection.Add(key, (HttpContext.Current.Request.Cookies[key]));
}
var res= cookieCollection.Where(x => x.Key.Contains("RequestVerificationToken")).First();
cookie = ((System.Web.HttpCookie)(res.Value)).Value;
string formToken = Convert.ToString(HttpContext.Current.Request.Headers["__RequestVerificationToken"]);
if(string.IsNullOrEmpty(formToken))
{
//To validate HTTP Post request
AntiForgery.Validate();
}
else
{
//To validate Ajax request
AntiForgery.Validate(cookie, formToken);
}
Other configurations which we have done are as below-
We have machine key in config which is same for all applications as well as on all web servers.
We have set AntiForgeryConfig.CookieName = "__RequestVerificationToken" + "_XYZ" cookie name across all applications which is mandatory to access token across applications.
Changes which we tried to resolve this issue-
We tried to post “__RequestVerificationToken” inside each ajax request’s data so that we can access it using Request.Form but no success on this.
We have verified that Content-Type header is appearing with each request.
Please suggest if you have any other way to implement CSRF functionality for Ajax POST requests across multiple applications.

Why can't I just send rest calls to MVC3?

I'm trying to send requests to my MVC3 app, I've tried regular WebRequest, I'm trying it with RestSharp applying correct Authenticator, but it still returns the redirect result of login page?
What am i doing wrong?
upd: How should I do forms authentication with RestSharp? I guess it's possible somehow - just need to play around that cookie...
If you are getting redirected to a login page your mvc 3 app must be setup for forms authentication. Forms authentication will want a cookie sent with the request. If you are using the basic authenticator in RestSharp this will not work. I assume that you are using the MVC controller to provide a REST API that you are trying to call.
One option is to upgrade to MVC 4 and use the ASP.NET Web API to develop your REST API's. The authorization behavior is a little different in an ASP.NET Web API in that it will return an HTTP 401 error instead of doing a redirect. And you can customize the AuthorizationAttribute to pull the information out of the HTTP header for basic authentication and authorization.
Another option is if the action on the controller does not require authentication/authorization you can put the AllowAnonymousAttribute on the method.
To pass the Forms authentication you gotta get the cookie and stick it to RestSharp's cookie container. To get the cookie you can use just regular WebRequest.
private Cookie GetAuthCookie(string user, string pass)
{
var http = WebRequest.Create(_baseUrl+"Users/Login") as HttpWebRequest;
http.AllowAutoRedirect = false;
http.Method = "POST";
http.ContentType = "application/x-www-form-urlencoded";
http.CookieContainer = new CookieContainer();
var postData = "UserName=" + user + "&Password=" + pass + "&RememberMe=true&RememberMe=false&ReturnUrl=www.google.com";
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(postData);
http.ContentLength = dataBytes.Length;
using (var postStream = http.GetRequestStream())
{
postStream.Write(dataBytes, 0, dataBytes.Length);
}
var httpResponse = http.GetResponse() as HttpWebResponse;
return httpResponse.Cookies[FormsAuthentication.FormsCookieName];
}

Resources