summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-05-15 06:17:43 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2012-05-15 06:17:43 -0700
commitcc109e4128ed7b8daad352ec76a5ec7d73d3f43a (patch)
tree23b1a5efbd2ddf38ba6a73ba1719275d31ff889a
parent9888a2fba665e90c5fcfc3dc731b6b254466db1b (diff)
parente29028dc6d11e1254b0c992c9872c00729001ed9 (diff)
downloadDotNetOpenAuth-cc109e4128ed7b8daad352ec76a5ec7d73d3f43a.zip
DotNetOpenAuth-cc109e4128ed7b8daad352ec76a5ec7d73d3f43a.tar.gz
DotNetOpenAuth-cc109e4128ed7b8daad352ec76a5ec7d73d3f43a.tar.bz2
Merge pull request #145 from dotnetjunky/v4.0a
Add protection against XSRF attacks
-rw-r--r--src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs96
1 files changed, 72 insertions, 24 deletions
diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
index 32e6b04..8327042 100644
--- a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
+++ b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.AspNet {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Web;
+ using System.Web.Security;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.Messaging;
@@ -23,6 +24,16 @@ namespace DotNetOpenAuth.AspNet {
private const string ProviderQueryStringName = "__provider__";
/// <summary>
+ /// The query string name for session id.
+ /// </summary>
+ private const string SessionIdQueryStringName = "__sid__";
+
+ /// <summary>
+ /// The cookie name for session id.
+ /// </summary>
+ private const string SessionIdCookieName = "__csid__";
+
+ /// <summary>
/// The _authentication provider.
/// </summary>
private readonly IAuthenticationClient authenticationProvider;
@@ -148,6 +159,20 @@ namespace DotNetOpenAuth.AspNet {
// attach the provider parameter so that we know which provider initiated
// the login when user is redirected back to this page
uri = uri.AttachQueryStringParameter(ProviderQueryStringName, this.authenticationProvider.ProviderName);
+
+ // Guard against XSRF attack by injecting session id into the redirect url and response cookie.
+ // Upon returning from the external provider, we'll compare the session id value in the query
+ // string and the cookie. If they don't match, we'll reject the request.
+ string sessionId = Guid.NewGuid().ToString();
+ uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId);
+
+ var xsrfCookie = new HttpCookie(SessionIdCookieName, sessionId);
+ if (FormsAuthentication.RequireSSL) {
+ xsrfCookie.Secure = true;
+ }
+ this.requestContext.Response.Cookies.Add(xsrfCookie);
+
+ // issue the redirect to the external auth provider
this.authenticationProvider.RequestAuthentication(this.requestContext, uri);
}
@@ -156,7 +181,7 @@ namespace DotNetOpenAuth.AspNet {
/// </summary>
/// <returns>The result of the authentication.</returns>
public AuthenticationResult VerifyAuthentication() {
- return this.VerifyAuthenticationCore(() => this.authenticationProvider.VerifyAuthentication(this.requestContext));
+ return VerifyAuthentication(returnUrl: null);
}
/// <summary>
@@ -164,13 +189,22 @@ namespace DotNetOpenAuth.AspNet {
/// </summary>
/// <param name="returnUrl">The return Url which must match exactly the Url passed into RequestAuthentication() earlier.</param>
/// <remarks>
- /// This method only applies to OAuth2 providers. For other providers, it ignores the returnUrl parameter.
+ /// This returnUrl parameter only applies to OAuth2 providers. For other providers, it ignores the returnUrl parameter.
/// </remarks>
/// <returns>
/// The result of the authentication.
/// </returns>
public AuthenticationResult VerifyAuthentication(string returnUrl) {
- Requires.NotNullOrEmpty(returnUrl, "returnUrl");
+ // check for XSRF attack
+ bool successful = this.ValidateRequestAgainstXsrfAttack();
+ if (!successful) {
+ return new AuthenticationResult(
+ isSuccessful: false,
+ provider: this.authenticationProvider.ProviderName,
+ providerUserId: null,
+ userName: null,
+ extraData: null);
+ }
// Only OAuth2 requires the return url value for the verify authenticaiton step
OAuth2Client oauth2Client = this.authenticationProvider as OAuth2Client;
@@ -188,36 +222,50 @@ namespace DotNetOpenAuth.AspNet {
// the login when user is redirected back to this page
uri = uri.AttachQueryStringParameter(ProviderQueryStringName, this.authenticationProvider.ProviderName);
- return this.VerifyAuthenticationCore(() => oauth2Client.VerifyAuthentication(this.requestContext, uri));
+ try {
+ AuthenticationResult result = oauth2Client.VerifyAuthentication(this.requestContext, uri);
+ if (!result.IsSuccessful) {
+ // if the result is a Failed result, creates a new Failed response which has providerName info.
+ result = new AuthenticationResult(
+ isSuccessful: false,
+ provider: this.authenticationProvider.ProviderName,
+ providerUserId: null,
+ userName: null,
+ extraData: null);
+ }
+
+ return result;
+ }
+ catch (HttpException exception) {
+ return new AuthenticationResult(exception.GetBaseException(), this.authenticationProvider.ProviderName);
+ }
}
else {
- return this.VerifyAuthentication();
+ return this.authenticationProvider.VerifyAuthentication(this.requestContext);
}
}
/// <summary>
- /// Helper to verify authentiation.
+ /// Validates the request against XSRF attack.
/// </summary>
- /// <param name="verifyAuthenticationCall">The real authentication action.</param>
- /// <returns>Authentication result</returns>
- private AuthenticationResult VerifyAuthenticationCore(Func<AuthenticationResult> verifyAuthenticationCall) {
- try {
- AuthenticationResult result = verifyAuthenticationCall();
- if (!result.IsSuccessful) {
- // if the result is a Failed result, creates a new Failed response which has providerName info.
- result = new AuthenticationResult(
- isSuccessful: false,
- provider: this.authenticationProvider.ProviderName,
- providerUserId: null,
- userName: null,
- extraData: null);
- }
+ /// <returns><c>true</c> if the request is safe. Otherwise, <c>false</c>.</returns>
+ private bool ValidateRequestAgainstXsrfAttack() {
+ // get the session id query string parameter
+ string queryStringSessionId = this.requestContext.Request.QueryString[SessionIdQueryStringName];
- return result;
- }
- catch (HttpException exception) {
- return new AuthenticationResult(exception.GetBaseException(), this.authenticationProvider.ProviderName);
+ // get the cookie id query string parameter
+ var cookie = this.requestContext.Request.Cookies[SessionIdCookieName];
+
+ bool successful = !string.IsNullOrEmpty(queryStringSessionId) &&
+ cookie != null &&
+ queryStringSessionId == cookie.Value;
+
+ if (successful) {
+ // be a good citizen, clean up cookie when the authentication succeeds
+ this.requestContext.Response.Cookies.Remove(SessionIdCookieName);
}
+
+ return successful;
}
#endregion