// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.WebUtilities; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; namespace Microsoft.AspNet.Mvc { /// /// Provides access to the anti-forgery system, which provides protection against /// Cross-site Request Forgery (XSRF, also called CSRF) attacks. /// public sealed class AntiForgery { private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1"; private readonly AntiForgeryWorker _worker; public AntiForgery([NotNull] IClaimUidExtractor claimUidExtractor, [NotNull] IDataProtectionProvider dataProtectionProvider, [NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider, [NotNull] IOptions antiforgeryOptions, [NotNull] IHtmlEncoder htmlEncoder, [NotNull] IOptions dataProtectionOptions) { var config = antiforgeryOptions.Options; var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty; config.CookieName = config.CookieName ?? ComputeCookieName(applicationId); var serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); var tokenStore = new AntiForgeryTokenStore(config, serializer); var tokenProvider = new AntiForgeryTokenProvider(config, claimUidExtractor, additionalDataProvider); _worker = new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider, htmlEncoder); } /// /// Generates an anti-forgery token for this request. This token can /// be validated by calling the Validate() method. /// /// The HTTP context associated with the current call. /// An HTML string corresponding to an <input type="hidden"> /// element. This element should be put inside a <form>. /// /// This method has a side effect: /// A response cookie is set if there is no valid cookie associated with the request. /// public TagBuilder GetHtml([NotNull] HttpContext context) { var builder = _worker.GetFormInputElement(context); return builder; } /// /// Generates an anti-forgery token pair (cookie and form token) for this request. /// This method is similar to GetHtml(HttpContext context), but this method gives the caller control /// over how to persist the returned values. To validate these tokens, call the /// appropriate overload of Validate. /// /// The HTTP context associated with the current call. /// The anti-forgery token - if any - that already existed /// for this request. May be null. The anti-forgery system will try to reuse this cookie /// value when generating a matching form token. /// /// Unlike the GetHtml(HttpContext context) method, this method has no side effect. The caller /// is responsible for setting the response cookie and injecting the returned /// form token as appropriate. /// public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCookieToken) { // Will contain a new cookie value if the old cookie token // was null or invalid. If this value is non-null when the method completes, the caller // must persist this value in the form of a response cookie, and the existing cookie value // should be discarded. If this value is null when the method completes, the existing // cookie value was valid and needn't be modified. return _worker.GetTokens(context, oldCookieToken); } /// /// Validates an anti-forgery token that was supplied for this request. /// The anti-forgery token may be generated by calling GetHtml(HttpContext context). /// /// The HTTP context associated with the current call. public async Task ValidateAsync([NotNull] HttpContext context) { await _worker.ValidateAsync(context); } /// /// Validates an anti-forgery token pair that was generated by the GetTokens method. /// /// The HTTP context associated with the current call. /// The token that was supplied in the request cookie. /// The token that was supplied in the request form body. public void Validate([NotNull] HttpContext context, string cookieToken, string formToken) { _worker.Validate(context, cookieToken, formToken); } /// /// Validates an anti-forgery token pair that was generated by the GetTokens method. /// /// The HTTP context associated with the current call. /// The anti-forgery token pair (cookie and form token) for this request. /// public void Validate([NotNull] HttpContext context, AntiForgeryTokenSet antiForgeryTokenSet) { Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken); } /// /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. /// /// The HTTP context associated with the current call. public void SetCookieTokenAndHeader([NotNull] HttpContext context) { _worker.SetCookieTokenAndHeader(context); } private string ComputeCookieName(string applicationId) { using (var sha256 = SHA256.Create()) { var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId)); var subHash = hash.Take(8).ToArray(); return WebEncoders.Base64UrlEncode(subHash); } } } }