In modern web programming, you can never have too many tokens. There are access tokens, refresh tokens, anti-XSRF tokens, and more. It’s the last type of token that I’ve gotten a lot of questions about recently. Specifically, does one need to protect against cross site requests forgeries when building an API based app? And if so, how does one create a token in an ASP.NET Core application?
In any application where the browser can implicitly authenticate the user, you’ll need to protect against cross-site request forgeries. Implicit authentication happens when the browser sends authentication information automatically, which is the case when using cookies for authentication, but also for applications using Windows authentication.
Generally, APIs don’t use cookies for authentication. Instead, APIs typically use bearer tokens, and custom JavaScript code running in the browser must send the token along by explicitly adding the token to a request.
However, there are also APIs living inside the same server process as a web application and using the same cookie as the application for authentication. This is the type of scenario where you must use anti forgery tokens to prevent an XSRF.
There is no additional work required to validate an anti-forgery token in an API request, because the [ValidateAntiForgeryToken] attribute in ASP.NET Core will look for tokens in a posted form input, or in an HTTP header. But, there is some additional work required to give the client a token. This is where the IAntiforgery service comes in.
[Route("api/[controller]")] public class XsrfTokenController : Controller { private readonly IAntiforgery _antiforgery; public XsrfTokenController(IAntiforgery antiforgery) { _antiforgery = antiforgery; } [HttpGet] public IActionResult Get() { var tokens = _antiforgery.GetAndStoreTokens(HttpContext); return new ObjectResult(new { token = tokens.RequestToken, tokenName = tokens.HeaderName }); } }
In the above code, we can inject the IAntiforgery service for an application and provide an endpoint a client can call to fetch the token and token name it needs to use in a request. The GetAndStoreTokens method will not only return a data structure with token information, it will also issue the anti-forgery cookie the framework will use in one-half of the validation algorithm. We can use a new ObjectResult to serialize the token information back to the client.
Note: if you want to change the header name, you can change the AntiForgeryOptions during startup of the application [1].
With the endpoint in place, you’ll need to fetch and store the token from JavaScript on the client. Here is a bit of Typescript code using Axios to fetch the token, then configure Axios to send the token with every HTTP request.
import axios, { AxiosResponse } from "axios"; import { IGolfer, IMatchSet } from "models" import { errorHandler } from "./error"; const XSRF_TOKEN_KEY = "xsrfToken"; const XSRF_TOKEN_NAME_KEY = "xsrfTokenName"; function reportError(message: string, response: AxiosResponse) { const formattedMessage = `${message} : Status ${response.status} ${response.statusText}` errorHandler.reportMessage(formattedMessage); } function setToken({token, tokenName}: { token: string, tokenName: string }) { window.sessionStorage.setItem(XSRF_TOKEN_KEY, token); window.sessionStorage.setItem(XSRF_TOKEN_NAME_KEY, tokenName); axios.defaults.headers.common[tokenName] = token; } function initializeXsrfToken() { let token = window.sessionStorage.getItem(XSRF_TOKEN_KEY); let tokenName = window.sessionStorage.getItem(XSRF_TOKEN_NAME_KEY); if (!token || !tokenName) { axios.get("/api/xsrfToken") .then(r => setToken(r.data)) .catch(r => reportError("Could not fetch XSRFTOKEN", r)); } else { setToken({ token: token, tokenName: tokenName }); } }
In this post we … well, forget it. No one reads these anyway.
[1] Tip: Using the name TolkeinToken can bring to life many literary references when discussing the application amongst team members.