Click here to Skip to main content
14,975,411 members
Articles / Security
Article
Posted 15 Apr 2016

Stats

29.9K views
594 downloads
5 bookmarked

XSRF with Web API and Angular

Rate me:
Please Sign up or sign in to vote.
4.64/5 (6 votes)
15 Apr 2016CPOL2 min read
Using XSRF with Web API and Angular

Introduction

In this article, I'm going to show how you can use the well known antiforgery infrastructure from ASP.NET MVC with ASP.NET Web API and Angular.

Background

First of all, let's talk about how the anti XSRF infrastructure works in MVC. When the Html.AntiForgeryToken line appears in the Razor markup, then two things happen: in the HTML, there will be a hidden input element which stores one half of the token and also a cookie will be attached to the response with the other token. After that, when the user submits the form, then the token from the hidden field will be on the request body and the cookie will naturally go as a cookie. :) On the server side, the AntiForgeryToken class will take care of validating if the tokens are correct or not.

Okay, now let's switch to SPA. In this case, we don't have a server side token as the markup will be generated on the client side. The solution I came up will be the following: I created a Web API endpoint which uses the "normal" AntiForgeryToken class to generate the tokens and it will send back the two tokens in the response body and as a cookie. I will render the token with an Angular directive and an interceptor will attach this token as an HTTP header. After that, on the server side, a custom filter will take care of the validation of the tokens.

You can find the solution attached to this article, or you can browse it on GitHub.

The Solution

The server side code will be very simple. There are two cases, if it is the first time in this session that we call this endpoint then we don't have any tokens. The GetTokens function first parameter is the cookie token, if we already have one, otherwise just provide an empty string. What is important is that the cookie token will be the same for the lifetime of the session and GetTokens will provide a null value if we provide the existing cookie value.

C#
[HttpGet]
[Route("antiforgerytoken")]
public HttpResponseMessage GetAntiForgeryToken()
{
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);

    HttpCookie cookie = HttpContext.Current.Request.Cookies["xsrf-token"];

    string cookieToken;
    string formToken;
    AntiForgery.GetTokens(cookie == null ? "" : cookie.Value, out cookieToken, out formToken);

    AntiForgeryTokenModel content = new AntiForgeryTokenModel
    {
        AntiForgeryToken = formToken
    };

    response.Content = new StringContent(
             JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");

    if (!string.IsNullOrEmpty(cookieToken))
    {
        response.Headers.AddCookies(new[]
        {
            new CookieHeaderValue("xsrf-token", cookieToken)
            {
                Expires = DateTimeOffset.Now.AddMinutes(10),
                Path = "/"
            }
        });
    }

    return response;
}

Next is the Angular directive. It will call the endpoint, then render the token as a hidden input element as MVC normally does.

JavaScript
(function() {
    'use strict';

    function antiForgeryDirectiveController(appService) {
        var directive = this;

        directive.antiForgeryToken = '';

        directive.activate = function () {
            appService.getAntiForgeryToken().then(function(data) {
                directive.antiForgeryToken = data.antiForgeryToken;;
            });
        };

        directive.activate();
    }

    function antiForgeryTokenDirective() {
        return {
            scope: {},
            controllerAs: 'directive',
            template: '<input id="__antiForgeryToken" 
            name="antiForgeryToken" type="hidden" 
            value="{{directive.antiForgeryToken}}" />',
            controller: [ 'appService', antiForgeryDirectiveController ]
        }
    }

    angular.module('demoApp').directive('antiforgerytoken', antiForgeryTokenDirective);
})();

We have another job at the client side. If the HTML contains the hidden input element (we can identify it by id), then we need to add an HTTP header to the request. For this purpose, we will use an interceptor:

JavaScript
(function() {
    'use strict';

    function antiForgeryInterceptor() {
        return {
            request: function($config) {
                var antiForgeryTokenField = document.getElementById('__antiForgeryToken');
                if (antiForgeryTokenField) {
                    var xsrfToken = antiForgeryTokenField.value;
                    $config.headers['XSRF-TOKEN'] = xsrfToken;
                }

                return $config;
            }
        };
    }

    angular.module('demoApp').service('antiForgeryInterceptor', antiForgeryInterceptor);
})();

The last step is the server side validation:

C#
public sealed class ValidateAntiForgeryTokenFilter : ActionFilterAttribute
{
    private const string XsrfHeader = "XSRF-TOKEN";
    private const string XsrfCookie = "xsrf-token";

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        HttpRequestHeaders headers = actionContext.Request.Headers;
        IEnumerable xsrfTokenList;

        if (!headers.TryGetValues(XsrfHeader, out xsrfTokenList))
        {
            actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            return;
        }

        string tokenHeaderValue = xsrfTokenList.First();

        CookieState tokenCookie = actionContext.Request.Headers.GetCookies().Select(c =&gt; 
                                  c[XsrfCookie]).FirstOrDefault();

        if (tokenCookie == null)
        {
            actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            return;
        }

        try
        {
            AntiForgery.Validate(tokenCookie.Value, tokenHeaderValue);
        }
        catch (HttpAntiForgeryException)
        {
            actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
        }
    }
}

After that, we can use this filter as the original ValidateAntyForgeryToken attribute.

History

  • April 15, 2016 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

ireiter
Hungary Hungary
Adventurer, Software developer, Writer, Tech enthusiast, Microsoft MVP

Comments and Discussions

 
QuestionUnable to get cookie token on web application Pin
adnan naseer shaikh18-Oct-17 6:26
Memberadnan naseer shaikh18-Oct-17 6:26 
AnswerRe: Unable to get cookie token on web application Pin
Member 1314738125-Jan-18 0:35
MemberMember 1314738125-Jan-18 0:35 
QuestionScenario Pin
Özgür Akdemirci10-Sep-17 16:44
MemberÖzgür Akdemirci10-Sep-17 16:44 
QuestionHow will this protect against a malicious page that makes a csrf attack with a GET or POST call after taking anti forgery token from this web api Get token call ? Pin Pin
Akshay Raut4-Aug-17 4:42
professionalAkshay Raut4-Aug-17 4:42 
AnswerRe: How will this protect against a malicious page that makes a csrf attack with a GET or POST call after taking anti forgery token from this web api Get token call ? Pin Pin
Member 1314738125-Jan-18 0:37
MemberMember 1314738125-Jan-18 0:37 
GeneralRe: How will this protect against a malicious page that makes a csrf attack with a GET or POST call after taking anti forgery token from this web api Get token call ? Pin Pin
Akshay Raut16-Jun-18 23:46
professionalAkshay Raut16-Jun-18 23:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.