Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implement the Anti-Forgery Token between MVC frontend and Web Api on different domains?

  1. I want to have the MVC project on www.example1.com
  2. WebApi project on api.example2.com

I want to restrict the access to WebApi. I've tried to implement the Anti-Forgery Token:

When I create the GET request to WebApi with Anti-forgery token then I get an exception because the request doesn't contains this token.

In method called ValidateRequestHeader is variable cookie = null.

How can I fix following code? Is this correct solution?

MVC project (front-end) - for development is localhost:33635:

Index.cshtml

<div class="container">


    <div class="row">

        <div class="col-md-12">


            <input id="get-request-button" type="button" class="btn btn-info" value="Create request to API Server" />

            <br />

            <div id="result"></div>

        </div>


    </div>


</div>


@section scripts
{

    <script type="text/javascript">

        @functions{
            public string TokenHeaderValue()
            {
                string cookieToken, formToken;
                AntiForgery.GetTokens(null, out cookieToken, out formToken);
                return cookieToken + ":" + formToken;
            }
        }

        $(function () {

            $("#get-request-button").click(function () {

                $.ajax("http://localhost:33887/api/values", {
                    type: "GET",
                    contentType: "application/json",
                    data: {},
                    dataType: "json",
                    headers: {
                        'RequestVerificationToken': '@TokenHeaderValue()'
                    }
                }).done(function (data) {
                    $("#result").html(data);
                });

                return false;
            });

        });


    </script>

}

WebApi project - for development is localhost:33887:

WebApiConfig.cs

public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            config.EnableCors(new EnableCorsAttribute("http://localhost:33635", "*", "*"));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
            );
        }

ValidateHttpAntiForgeryTokenAttribute.cs:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public sealed class ValidateHttpAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
    {
        public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            var request = actionContext.Request;

            try
            {
                if (IsAjaxRequest(request))
                {
                    ValidateRequestHeader(request);
                }
                else
                {
                    AntiForgery.Validate();
                }
            }
            catch (Exception)
            {
                actionContext.Response = new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.Forbidden,
                    RequestMessage = actionContext.ControllerContext.Request
                };
                return FromResult(actionContext.Response);
            }
            return continuation();
        }

        private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
        {
            var source = new TaskCompletionSource<HttpResponseMessage>();
            source.SetResult(result);
            return source.Task;
        }

        private bool IsAjaxRequest(HttpRequestMessage request)
        {
            IEnumerable<string> xRequestedWithHeaders;
            if (!request.Headers.TryGetValues("X-Requested-With", out xRequestedWithHeaders)) return false;

            var headerValue = xRequestedWithHeaders.FirstOrDefault();

            return !String.IsNullOrEmpty(headerValue) && String.Equals(headerValue, "XMLHttpRequest", StringComparison.OrdinalIgnoreCase);
        }

        private void ValidateRequestHeader(HttpRequestMessage request)
        {
            var headers = request.Headers;
            var cookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName])
                    .FirstOrDefault();

            IEnumerable<string> xXsrfHeaders;

            if (headers.TryGetValues("RequestVerificationToken", out xXsrfHeaders))
            {
                var rvt = xXsrfHeaders.FirstOrDefault();

                if (cookie == null)
                {
                    throw new InvalidOperationException($"Missing {AntiForgeryConfig.CookieName} cookie");
                }

                AntiForgery.Validate(cookie.Value, rvt);
            }
            else
            {
                var headerBuilder = new StringBuilder();

                headerBuilder.AppendLine("Missing X-XSRF-Token HTTP header:");

                foreach (var header in headers)
                {
                    headerBuilder.AppendFormat("- [{0}] = {1}", header.Key, header.Value);
                    headerBuilder.AppendLine();
                }

                throw new InvalidOperationException(headerBuilder.ToString());
            }
        }
    }

ValuesController:

public class ValuesController : ApiController
    {
        // GET: api/Values
        [ValidateHttpAntiForgeryToken]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Values/5
        public string Get(int id)
        {
            return "value";
        }

        // POST: api/Values
        public void Post([FromBody]string value)
        {
        }

        // PUT: api/Values/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE: api/Values/5
        public void Delete(int id)
        {
        }
    }
like image 716
Jenan Avatar asked Oct 29 '15 09:10

Jenan


People also ask

Where are anti-forgery tokens stored?

ASP.NET Core uses a hidden field to store the anti-forgery token and uses the ValidateAntiForgeryToken attribute to validate the token. As the token is sent to the browser in a hidden field, it is also stored in an HttpOnly cookie.

What is cross-site request forgery in MVC?

Cross-site request forgery (CSRF) is an attack that tricks an end user into executing undesirable actions while logged into a web application. Taking advantage of the authenticated user's permissions, a CSRF attack dupes the victim into performing specific actions that benefit the attacker.


1 Answers

This is not the way to restrict the access by another service. The ForgeryToken helps to prevent CSRF attacks, ASP.NET MVC uses anti-forgery tokens, also called request verification tokens. The client requests an HTML page that contains a form. The server includes two tokens in the response. One token is sent as a cookie. When you submit the form, those will match on the same server.

I believe, What you need is the trust from example1.com to api.example2.com. An example would be stackauth which is a domain for centralized services across the entire Stack Exchange network. Then you can implement the authorization as you need in your project.

like image 133
Amirhossein Mehrvarzi Avatar answered Oct 04 '22 09:10

Amirhossein Mehrvarzi