diff --git a/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs b/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs
new file mode 100644
index 0000000000..ab2d2b7c31
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs
@@ -0,0 +1,139 @@
+// 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.Antiforgery.Internal;
+using Microsoft.AspNet.DataProtection;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.WebUtilities;
+using Microsoft.Framework.Internal;
+using Microsoft.Framework.OptionsModel;
+using Microsoft.Framework.WebEncoders;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// 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.Antiforgery.AntiforgeryToken.v1";
+ private readonly AntiforgeryWorker _worker;
+
+ public Antiforgery(
+ [NotNull] IClaimUidExtractor claimUidExtractor,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] IAntiforgeryAdditionalDataProvider additionalDataProvider,
+ [NotNull] IOptions AntiforgeryOptionsAccessor,
+ [NotNull] IHtmlEncoder htmlEncoder,
+ [NotNull] IOptions dataProtectionOptions)
+ {
+ var AntiforgeryOptions = AntiforgeryOptionsAccessor.Options;
+ var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty;
+ AntiforgeryOptions.CookieName = AntiforgeryOptions.CookieName ?? ComputeCookieName(applicationId);
+
+ var serializer = new AntiforgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose));
+ var tokenStore = new AntiforgeryTokenStore(AntiforgeryOptions, serializer);
+ var tokenProvider = new AntiforgeryTokenProvider(AntiforgeryOptions, claimUidExtractor, additionalDataProvider);
+ _worker = new AntiforgeryWorker(serializer, AntiforgeryOptions, 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 string GetHtml([NotNull] HttpContext context)
+ {
+ var html = _worker.GetFormInputElement(context);
+ return html;
+ }
+
+ ///
+ /// 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryContext.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryContext.cs
new file mode 100644
index 0000000000..ded759255c
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryContext.cs
@@ -0,0 +1,13 @@
+// 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.
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// Used as a per request state.
+ ///
+ public class AntiforgeryContext
+ {
+ public AntiforgeryToken CookieToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryContextAccessor.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryContextAccessor.cs
new file mode 100644
index 0000000000..3a2806a10f
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryContextAccessor.cs
@@ -0,0 +1,10 @@
+// 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.
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ public class AntiforgeryContextAccessor : IAntiforgeryContextAccessor
+ {
+ public AntiforgeryContext Value { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs
new file mode 100644
index 0000000000..50f8f42b51
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs
@@ -0,0 +1,81 @@
+// 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 Microsoft.Framework.Internal;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// Provides programmatic configuration for the anti-forgery token system.
+ ///
+ public class AntiforgeryOptions
+ {
+ private const string AntiforgeryTokenFieldName = "__RequestVerificationToken";
+
+ private string _cookieName;
+ private string _formFieldName = AntiforgeryTokenFieldName;
+
+ ///
+ /// Specifies the name of the cookie that is used by the anti-forgery
+ /// system.
+ ///
+ ///
+ /// If an explicit name is not provided, the system will automatically
+ /// generate a name.
+ ///
+ public string CookieName
+ {
+ get
+ {
+ return _cookieName;
+ }
+
+ [param: NotNull]
+ set
+ {
+ _cookieName = value;
+ }
+ }
+
+ ///
+ /// Specifies the name of the anti-forgery token field that is used by the anti-forgery system.
+ ///
+ public string FormFieldName
+ {
+ get
+ {
+ return _formFieldName;
+ }
+
+ [param: NotNull]
+ set
+ {
+ _formFieldName = value;
+ }
+ }
+
+ ///
+ /// Specifies whether SSL is required for the anti-forgery system
+ /// to operate. If this setting is 'true' and a non-SSL request
+ /// comes into the system, all anti-forgery APIs will fail.
+ ///
+ public bool RequireSSL
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Specifies whether to suppress the generation of X-Frame-Options header
+ /// which is used to prevent ClickJacking. By default, the X-Frame-Options
+ /// header is generated with the value SAMEORIGIN. If this setting is 'true',
+ /// the X-Frame-Options header will not be generated for the response.
+ ///
+ public bool SuppressXFrameOptionsHeader
+ {
+ get;
+ set;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs
new file mode 100644
index 0000000000..61f9665c26
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs
@@ -0,0 +1,53 @@
+// 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.
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ public sealed class AntiforgeryToken
+ {
+ internal const int SecurityTokenBitLength = 128;
+ internal const int ClaimUidBitLength = 256;
+
+ private string _additionalData = string.Empty;
+ private string _username = string.Empty;
+ private BinaryBlob _securityToken;
+
+ public string AdditionalData
+ {
+ get { return _additionalData; }
+ set
+ {
+ _additionalData = value ?? string.Empty;
+ }
+ }
+
+ public BinaryBlob ClaimUid { get; set; }
+
+ public bool IsSessionToken { get; set; }
+
+ public BinaryBlob SecurityToken
+ {
+ get
+ {
+ if (_securityToken == null)
+ {
+ _securityToken = new BinaryBlob(SecurityTokenBitLength);
+ }
+ return _securityToken;
+ }
+ set
+ {
+ _securityToken = value;
+ }
+ }
+
+ public string Username
+ {
+ get { return _username; }
+ set
+ {
+ _username = value ?? string.Empty;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs
new file mode 100644
index 0000000000..cdb02b986f
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs
@@ -0,0 +1,168 @@
+// 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.Diagnostics;
+using System.Security.Claims;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ public sealed class AntiforgeryTokenProvider : IAntiforgeryTokenValidator, IAntiforgeryTokenGenerator
+ {
+ private readonly IClaimUidExtractor _claimUidExtractor;
+ private readonly AntiforgeryOptions _config;
+ private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider;
+
+ internal AntiforgeryTokenProvider(
+ AntiforgeryOptions config,
+ IClaimUidExtractor claimUidExtractor,
+ IAntiforgeryAdditionalDataProvider additionalDataProvider)
+ {
+ _config = config;
+ _claimUidExtractor = claimUidExtractor;
+ _additionalDataProvider = additionalDataProvider;
+ }
+
+ public AntiforgeryToken GenerateCookieToken()
+ {
+ return new AntiforgeryToken()
+ {
+ // SecurityToken will be populated automatically.
+ IsSessionToken = true
+ };
+ }
+
+ public AntiforgeryToken GenerateFormToken(HttpContext httpContext,
+ ClaimsIdentity identity,
+ AntiforgeryToken cookieToken)
+ {
+ Debug.Assert(IsCookieTokenValid(cookieToken));
+
+ var formToken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ IsSessionToken = false
+ };
+
+ var isIdentityAuthenticated = false;
+
+ // populate Username and ClaimUid
+ if (identity != null && identity.IsAuthenticated)
+ {
+ isIdentityAuthenticated = true;
+ formToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity));
+ if (formToken.ClaimUid == null)
+ {
+ formToken.Username = identity.Name;
+ }
+ }
+
+ // populate AdditionalData
+ if (_additionalDataProvider != null)
+ {
+ formToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext);
+ }
+
+ if (isIdentityAuthenticated
+ && string.IsNullOrEmpty(formToken.Username)
+ && formToken.ClaimUid == null
+ && string.IsNullOrEmpty(formToken.AdditionalData))
+ {
+ // Application says user is authenticated, but we have no identifier for the user.
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(identity.GetType()));
+ }
+
+ return formToken;
+ }
+
+ public bool IsCookieTokenValid(AntiforgeryToken cookieToken)
+ {
+ return (cookieToken != null && cookieToken.IsSessionToken);
+ }
+
+ public void ValidateTokens(
+ HttpContext httpContext,
+ ClaimsIdentity identity,
+ AntiforgeryToken sessionToken,
+ AntiforgeryToken fieldToken)
+ {
+ // Were the tokens even present at all?
+ if (sessionToken == null)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryToken_CookieMissing(_config.CookieName));
+ }
+ if (fieldToken == null)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryToken_FormFieldMissing(_config.FormFieldName));
+ }
+
+ // Do the tokens have the correct format?
+ if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryToken_TokensSwapped(_config.CookieName, _config.FormFieldName));
+ }
+
+ // Are the security tokens embedded in each incoming token identical?
+ if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
+ {
+ throw new InvalidOperationException(Resources.AntiforgeryToken_SecurityTokenMismatch);
+ }
+
+ // Is the incoming token meant for the current user?
+ var currentUsername = string.Empty;
+ BinaryBlob currentClaimUid = null;
+
+ if (identity != null && identity.IsAuthenticated)
+ {
+ currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity));
+ if (currentClaimUid == null)
+ {
+ currentUsername = identity.Name ?? string.Empty;
+ }
+ }
+
+ // OpenID and other similar authentication schemes use URIs for the username.
+ // These should be treated as case-sensitive.
+ var useCaseSensitiveUsernameComparison =
+ currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
+ currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
+
+ if (!string.Equals(fieldToken.Username,
+ currentUsername,
+ (useCaseSensitiveUsernameComparison) ?
+ StringComparison.Ordinal :
+ StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryToken_UsernameMismatch(fieldToken.Username, currentUsername));
+ }
+
+ if (!Equals(fieldToken.ClaimUid, currentClaimUid))
+ {
+ throw new InvalidOperationException(Resources.AntiforgeryToken_ClaimUidMismatch);
+ }
+
+ // Is the AdditionalData valid?
+ if (_additionalDataProvider != null &&
+ !_additionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
+ {
+ throw new InvalidOperationException(Resources.AntiforgeryToken_AdditionalDataCheckFailed);
+ }
+ }
+
+ private static BinaryBlob GetClaimUidBlob(string base64ClaimUid)
+ {
+ if (base64ClaimUid == null)
+ {
+ return null;
+ }
+
+ return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs
new file mode 100644
index 0000000000..9022f77b13
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs
@@ -0,0 +1,135 @@
+// 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.IO;
+using Microsoft.AspNet.DataProtection;
+using Microsoft.AspNet.WebUtilities;
+using Microsoft.Framework.Internal;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ public sealed class AntiforgeryTokenSerializer : IAntiforgeryTokenSerializer
+ {
+ private readonly IDataProtector _cryptoSystem;
+ private const byte TokenVersion = 0x01;
+
+ public AntiforgeryTokenSerializer([NotNull] IDataProtector cryptoSystem)
+ {
+ _cryptoSystem = cryptoSystem;
+ }
+
+ public AntiforgeryToken Deserialize(string serializedToken)
+ {
+ Exception innerException = null;
+ try
+ {
+ var tokenBytes = WebEncoders.Base64UrlDecode(serializedToken);
+ using (var stream = new MemoryStream(_cryptoSystem.Unprotect(tokenBytes)))
+ {
+ using (var reader = new BinaryReader(stream))
+ {
+ var token = DeserializeImpl(reader);
+ if (token != null)
+ {
+ return token;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // swallow all exceptions - homogenize error if something went wrong
+ innerException = ex;
+ }
+
+ // if we reached this point, something went wrong deserializing
+ throw new InvalidOperationException(Resources.AntiforgeryToken_DeserializationFailed, innerException);
+ }
+
+ /* The serialized format of the anti-XSRF token is as follows:
+ * Version: 1 byte integer
+ * SecurityToken: 16 byte binary blob
+ * IsSessionToken: 1 byte Boolean
+ * [if IsSessionToken != true]
+ * +- IsClaimsBased: 1 byte Boolean
+ * | [if IsClaimsBased = true]
+ * | `- ClaimUid: 32 byte binary blob
+ * | [if IsClaimsBased = false]
+ * | `- Username: UTF-8 string with 7-bit integer length prefix
+ * `- AdditionalData: UTF-8 string with 7-bit integer length prefix
+ */
+ private static AntiforgeryToken DeserializeImpl(BinaryReader reader)
+ {
+ // we can only consume tokens of the same serialized version that we generate
+ var embeddedVersion = reader.ReadByte();
+ if (embeddedVersion != TokenVersion)
+ {
+ return null;
+ }
+
+ var deserializedToken = new AntiforgeryToken();
+ var securityTokenBytes = reader.ReadBytes(AntiforgeryToken.SecurityTokenBitLength / 8);
+ deserializedToken.SecurityToken =
+ new BinaryBlob(AntiforgeryToken.SecurityTokenBitLength, securityTokenBytes);
+ deserializedToken.IsSessionToken = reader.ReadBoolean();
+
+ if (!deserializedToken.IsSessionToken)
+ {
+ var isClaimsBased = reader.ReadBoolean();
+ if (isClaimsBased)
+ {
+ var claimUidBytes = reader.ReadBytes(AntiforgeryToken.ClaimUidBitLength / 8);
+ deserializedToken.ClaimUid = new BinaryBlob(AntiforgeryToken.ClaimUidBitLength, claimUidBytes);
+ }
+ else
+ {
+ deserializedToken.Username = reader.ReadString();
+ }
+
+ deserializedToken.AdditionalData = reader.ReadString();
+ }
+
+ // if there's still unconsumed data in the stream, fail
+ if (reader.BaseStream.ReadByte() != -1)
+ {
+ return null;
+ }
+
+ // success
+ return deserializedToken;
+ }
+
+ public string Serialize([NotNull] AntiforgeryToken token)
+ {
+ using (var stream = new MemoryStream())
+ {
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write(TokenVersion);
+ writer.Write(token.SecurityToken.GetData());
+ writer.Write(token.IsSessionToken);
+
+ if (!token.IsSessionToken)
+ {
+ if (token.ClaimUid != null)
+ {
+ writer.Write(true /* isClaimsBased */);
+ writer.Write(token.ClaimUid.GetData());
+ }
+ else
+ {
+ writer.Write(false /* isClaimsBased */);
+ writer.Write(token.Username);
+ }
+
+ writer.Write(token.AdditionalData);
+ }
+
+ writer.Flush();
+ return WebEncoders.Base64UrlEncode(_cryptoSystem.Protect(stream.ToArray()));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs
new file mode 100644
index 0000000000..c18e15dd4f
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs
@@ -0,0 +1,42 @@
+// 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;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// The anti-forgery token pair (cookie and form token) for a request.
+ ///
+ public class AntiforgeryTokenSet
+ {
+ ///
+ /// Creates the anti-forgery token pair (cookie and form token) for a request.
+ ///
+ /// The token that is supplied in the request form body.
+ /// The token that is supplied in the request cookie.
+ public AntiforgeryTokenSet(string formToken, string cookieToken)
+ {
+ if (string.IsNullOrEmpty(formToken))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(formToken));
+ }
+
+ FormToken = formToken;
+
+ // Cookie Token is allowed to be null in the case when the old cookie is valid
+ // and there is no new cookieToken generated.
+ CookieToken = cookieToken;
+ }
+
+ ///
+ /// The token that is supplied in the request form body.
+ ///
+ public string FormToken { get; private set; }
+
+ /// The cookie token is allowed to be null.
+ /// This would be the case when the old cookie token is still valid.
+ /// In such cases a call to GetTokens would return a token set with null cookie token.
+ public string CookieToken { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs
new file mode 100644
index 0000000000..e288c1c786
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs
@@ -0,0 +1,79 @@
+// 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.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.Internal;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form
+ public sealed class AntiforgeryTokenStore : IAntiforgeryTokenStore
+ {
+ private readonly AntiforgeryOptions _config;
+ private readonly IAntiforgeryTokenSerializer _serializer;
+
+ public AntiforgeryTokenStore([NotNull] AntiforgeryOptions config,
+ [NotNull] IAntiforgeryTokenSerializer serializer)
+ {
+ _config = config;
+ _serializer = serializer;
+ }
+
+ public AntiforgeryToken GetCookieToken(HttpContext httpContext)
+ {
+ var contextAccessor =
+ httpContext.RequestServices.GetRequiredService();
+ if (contextAccessor.Value != null)
+ {
+ return contextAccessor.Value.CookieToken;
+ }
+
+ var requestCookie = httpContext.Request.Cookies[_config.CookieName];
+ if (string.IsNullOrEmpty(requestCookie))
+ {
+ // unable to find the cookie.
+ return null;
+ }
+
+ return _serializer.Deserialize(requestCookie);
+ }
+
+ public async Task GetFormTokenAsync(HttpContext httpContext)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
+ var value = form[_config.FormFieldName];
+ if (string.IsNullOrEmpty(value))
+ {
+ // did not exist
+ return null;
+ }
+
+ return _serializer.Deserialize(value);
+ }
+
+ public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token)
+ {
+ // Add the cookie to the request based context.
+ // This is useful if the cookie needs to be reloaded in the context of the same request.
+ var contextAccessor =
+ httpContext.RequestServices.GetRequiredService();
+ Debug.Assert(contextAccessor.Value == null, "AntiforgeryContext should be set only once per request.");
+ contextAccessor.Value = new AntiforgeryContext() { CookieToken = token };
+
+ var serializedToken = _serializer.Serialize(token);
+ var options = new CookieOptions() { HttpOnly = true };
+
+ // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default
+ // value of newCookie.Secure is poulated out of band.
+ if (_config.RequireSSL)
+ {
+ options.Secure = true;
+ }
+
+ httpContext.Response.Cookies.Append(_config.CookieName, serializedToken, options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/BinaryBlob.cs b/src/Microsoft.AspNet.Antiforgery/BinaryBlob.cs
new file mode 100644
index 0000000000..e0faebed0a
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/BinaryBlob.cs
@@ -0,0 +1,117 @@
+// 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.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Represents a binary blob (token) that contains random data.
+ // Useful for binary data inside a serialized stream.
+ [DebuggerDisplay("{DebuggerString}")]
+ public sealed class BinaryBlob : IEquatable
+ {
+ private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create();
+ private readonly byte[] _data;
+
+ // Generates a new token using a specified bit length.
+ public BinaryBlob(int bitLength)
+ : this(bitLength, GenerateNewToken(bitLength))
+ {
+ }
+
+ // Generates a token using an existing binary value.
+ public BinaryBlob(int bitLength, byte[] data)
+ {
+ if (bitLength < 32 || bitLength % 8 != 0)
+ {
+ throw new ArgumentOutOfRangeException("bitLength");
+ }
+ if (data == null || data.Length != bitLength / 8)
+ {
+ throw new ArgumentOutOfRangeException("data");
+ }
+
+ _data = data;
+ }
+
+ public int BitLength
+ {
+ get
+ {
+ return checked(_data.Length * 8);
+ }
+ }
+
+ private string DebuggerString
+ {
+ get
+ {
+ var sb = new StringBuilder("0x", 2 + (_data.Length * 2));
+ for (var i = 0; i < _data.Length; i++)
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", _data[i]);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BinaryBlob);
+ }
+
+ public bool Equals(BinaryBlob other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ Debug.Assert(this._data.Length == other._data.Length);
+ return AreByteArraysEqual(this._data, other._data);
+ }
+
+ public byte[] GetData()
+ {
+ return _data;
+ }
+
+ public override int GetHashCode()
+ {
+ // Since data should contain uniformly-distributed entropy, the
+ // first 32 bits can serve as the hash code.
+ Debug.Assert(_data != null && _data.Length >= (32 / 8));
+ return BitConverter.ToInt32(_data, 0);
+ }
+
+ private static byte[] GenerateNewToken(int bitLength)
+ {
+ var data = new byte[bitLength / 8];
+ _randomNumberGenerator.GetBytes(data);
+ return data;
+ }
+
+ // Need to mark it with NoInlining and NoOptimization attributes to ensure that the
+ // operation runs in constant time.
+ [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ private static bool AreByteArraysEqual(byte[] a, byte[] b)
+ {
+ if (a == null || b == null || a.Length != b.Length)
+ {
+ return false;
+ }
+
+ var areEqual = true;
+ for (var i = 0; i < a.Length; i++)
+ {
+ areEqual &= (a[i] == b[i]);
+ }
+ return areEqual;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryAdditionalDataProvider.cs b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryAdditionalDataProvider.cs
new file mode 100644
index 0000000000..2d5967f368
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryAdditionalDataProvider.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// A default implementation.
+ ///
+ public class DefaultAntiforgeryAdditionalDataProvider : IAntiforgeryAdditionalDataProvider
+ {
+ ///
+ public virtual string GetAdditionalData(HttpContext context)
+ {
+ return string.Empty;
+ }
+
+ ///
+ public virtual bool ValidateAdditionalData(HttpContext context, string additionalData)
+ {
+ // Default implementation does not understand anything but empty data.
+ return string.IsNullOrEmpty(additionalData);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultClaimUidExtractor.cs b/src/Microsoft.AspNet.Antiforgery/DefaultClaimUidExtractor.cs
new file mode 100644
index 0000000000..7c033547cc
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/DefaultClaimUidExtractor.cs
@@ -0,0 +1,82 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Claims;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// Default implementation of .
+ ///
+ public class DefaultClaimUidExtractor : IClaimUidExtractor
+ {
+ ///
+ public string ExtractClaimUid(ClaimsIdentity claimsIdentity)
+ {
+ if (claimsIdentity == null || !claimsIdentity.IsAuthenticated)
+ {
+ // Skip anonymous users
+ return null;
+ }
+
+ var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity);
+ var claimUidBytes = ComputeSHA256(uniqueIdentifierParameters);
+ return Convert.ToBase64String(claimUidBytes);
+ }
+
+ // Internal for testing
+ internal static IEnumerable GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity)
+ {
+ var nameIdentifierClaim = claimsIdentity.FindFirst(claim =>
+ String.Equals(ClaimTypes.NameIdentifier,
+ claim.Type, StringComparison.Ordinal));
+ if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value))
+ {
+ return new string[]
+ {
+ ClaimTypes.NameIdentifier,
+ nameIdentifierClaim.Value
+ };
+ }
+
+ // We do not understand this ClaimsIdentity, fallback on serializing the entire claims Identity.
+ var claims = claimsIdentity.Claims.ToList();
+ claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
+ var identifierParameters = new List();
+ foreach (var claim in claims)
+ {
+ identifierParameters.Add(claim.Type);
+ identifierParameters.Add(claim.Value);
+ }
+
+ return identifierParameters;
+ }
+
+ private static byte[] ComputeSHA256(IEnumerable parameters)
+ {
+ using (var stream = new MemoryStream())
+ {
+ using (var writer = new BinaryWriter(stream))
+ {
+ foreach (string parameter in parameters)
+ {
+ writer.Write(parameter); // also writes the length as a prefix; unambiguous
+ }
+
+ writer.Flush();
+
+ using (var sha256 = SHA256.Create())
+ {
+ var bytes = sha256.ComputeHash(stream.ToArray(), 0, checked((int)stream.Length));
+ return bytes;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryAdditionalDataProvider.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryAdditionalDataProvider.cs
new file mode 100644
index 0000000000..fb609768bc
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryAdditionalDataProvider.cs
@@ -0,0 +1,39 @@
+// 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 Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// Allows providing or validating additional custom data for anti-forgery tokens.
+ /// For example, the developer could use this to supply a nonce when the token is
+ /// generated, then he could validate the nonce when the token is validated.
+ ///
+ ///
+ /// The anti-forgery system already embeds the client's username within the
+ /// generated tokens. This interface provides and consumes supplemental
+ /// data. If an incoming anti-forgery token contains supplemental data but no
+ /// additional data provider is configured, the supplemental data will not be
+ /// validated.
+ ///
+ public interface IAntiforgeryAdditionalDataProvider
+ {
+ ///
+ /// Provides additional data to be stored for the anti-forgery tokens generated
+ /// during this request.
+ ///
+ /// Information about the current request.
+ /// Supplemental data to embed within the anti-forgery token.
+ string GetAdditionalData(HttpContext context);
+
+ ///
+ /// Validates additional data that was embedded inside an incoming anti-forgery
+ /// token.
+ ///
+ /// Information about the current request.
+ /// Supplemental data that was embedded within the token.
+ /// True if the data is valid; false if the data is invalid.
+ bool ValidateAdditionalData(HttpContext context, string additionalData);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryContextAccessor.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryContextAccessor.cs
new file mode 100644
index 0000000000..ac2c5e023f
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryContextAccessor.cs
@@ -0,0 +1,10 @@
+// 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.
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ public interface IAntiforgeryContextAccessor
+ {
+ AntiforgeryContext Value { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs
new file mode 100644
index 0000000000..87d936dec3
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs
@@ -0,0 +1,22 @@
+// 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.Security.Claims;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Provides configuration information about the anti-forgery system.
+ public interface IAntiforgeryTokenGenerator
+ {
+ // Generates a new random cookie token.
+ AntiforgeryToken GenerateCookieToken();
+
+ // Given a cookie token, generates a corresponding form token.
+ // The incoming cookie token must be valid.
+ AntiforgeryToken GenerateFormToken(
+ HttpContext httpContext,
+ ClaimsIdentity identity,
+ AntiforgeryToken cookieToken);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenSerializer.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenSerializer.cs
new file mode 100644
index 0000000000..4ba30c5591
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenSerializer.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Abstracts out the serialization process for an anti-forgery token
+ public interface IAntiforgeryTokenSerializer
+ {
+ AntiforgeryToken Deserialize(string serializedToken);
+ string Serialize(AntiforgeryToken token);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs
new file mode 100644
index 0000000000..0f2ab27024
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs
@@ -0,0 +1,16 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Provides an abstraction around how tokens are persisted and retrieved for a request
+ public interface IAntiforgeryTokenStore
+ {
+ AntiforgeryToken GetCookieToken(HttpContext httpContext);
+ Task GetFormTokenAsync(HttpContext httpContext);
+ void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs
new file mode 100644
index 0000000000..ed75a9de45
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs
@@ -0,0 +1,23 @@
+// 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.Security.Claims;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ // Provides an abstraction around something that can validate anti-XSRF tokens
+ public interface IAntiforgeryTokenValidator
+ {
+ // Determines whether an existing cookie token is valid (well-formed).
+ // If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken().
+ bool IsCookieTokenValid(AntiforgeryToken cookieToken);
+
+ // Validates a (cookie, form) token pair.
+ void ValidateTokens(
+ HttpContext httpContext,
+ ClaimsIdentity identity,
+ AntiforgeryToken cookieToken,
+ AntiforgeryToken formToken);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/IClaimUidExtractor.cs b/src/Microsoft.AspNet.Antiforgery/IClaimUidExtractor.cs
new file mode 100644
index 0000000000..c7b0af04c9
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/IClaimUidExtractor.cs
@@ -0,0 +1,20 @@
+// 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.Security.Claims;
+
+namespace Microsoft.AspNet.Antiforgery
+{
+ ///
+ /// This interface can extract unique identifers for a claims-based identity.
+ ///
+ public interface IClaimUidExtractor
+ {
+ ///
+ /// Extracts claims identifier.
+ ///
+ /// The .
+ /// The claims identifier.
+ string ExtractClaimUid(ClaimsIdentity identity);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs b/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs
new file mode 100644
index 0000000000..ac4dd9e0b8
--- /dev/null
+++ b/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs
@@ -0,0 +1,251 @@
+// 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.Diagnostics;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.Framework.Internal;
+using Microsoft.Framework.WebEncoders;
+
+namespace Microsoft.AspNet.Antiforgery.Internal
+{
+ public sealed class AntiforgeryWorker
+ {
+ private readonly AntiforgeryOptions _config;
+ private readonly IAntiforgeryTokenSerializer _serializer;
+ private readonly IAntiforgeryTokenStore _tokenStore;
+ private readonly IAntiforgeryTokenValidator _validator;
+ private readonly IAntiforgeryTokenGenerator _generator;
+ private readonly IHtmlEncoder _htmlEncoder;
+
+ public AntiforgeryWorker(
+ [NotNull] IAntiforgeryTokenSerializer serializer,
+ [NotNull] AntiforgeryOptions config,
+ [NotNull] IAntiforgeryTokenStore tokenStore,
+ [NotNull] IAntiforgeryTokenGenerator generator,
+ [NotNull] IAntiforgeryTokenValidator validator,
+ [NotNull] IHtmlEncoder htmlEncoder)
+ {
+ _serializer = serializer;
+ _config = config;
+ _tokenStore = tokenStore;
+ _generator = generator;
+ _validator = validator;
+ _htmlEncoder = htmlEncoder;
+ }
+
+ private void CheckSSLConfig(HttpContext httpContext)
+ {
+ if (_config.RequireSSL && !httpContext.Request.IsHttps)
+ {
+ throw new InvalidOperationException(Resources.AntiforgeryWorker_RequireSSL);
+ }
+ }
+
+ private AntiforgeryToken DeserializeToken(string serializedToken)
+ {
+ return (!string.IsNullOrEmpty(serializedToken))
+ ? _serializer.Deserialize(serializedToken)
+ : null;
+ }
+
+ private AntiforgeryToken DeserializeTokenDoesNotThrow(string serializedToken)
+ {
+ try
+ {
+ return DeserializeToken(serializedToken);
+ }
+ catch
+ {
+ // ignore failures since we'll just generate a new token
+ return null;
+ }
+ }
+
+ private static ClaimsIdentity ExtractIdentity(HttpContext httpContext)
+ {
+ if (httpContext != null)
+ {
+ var user = httpContext.User;
+
+ if (user != null)
+ {
+ // We only support ClaimsIdentity.
+ return user.Identity as ClaimsIdentity;
+ }
+ }
+
+ return null;
+ }
+
+ private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext)
+ {
+ try
+ {
+ return _tokenStore.GetCookieToken(httpContext);
+ }
+ catch
+ {
+ // ignore failures since we'll just generate a new token
+ return null;
+ }
+ }
+
+ // [ ENTRY POINT ]
+ // Generates an anti-XSRF token pair for the current user. The return
+ // value is the hidden input form element that should be rendered in
+ // the