summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs')
-rw-r--r--src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs129
1 files changed, 98 insertions, 31 deletions
diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
index 06ca161..d33913a 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;
@@ -140,44 +151,55 @@ namespace DotNetOpenAuth.AspNet {
Uri uri;
if (!string.IsNullOrEmpty(returnUrl)) {
uri = UriHelper.ConvertToAbsoluteUri(returnUrl, this.requestContext);
- } else {
+ }
+ else {
uri = this.requestContext.Request.GetPublicFacingUrl();
}
// 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);
- this.authenticationProvider.RequestAuthentication(this.requestContext, uri);
- }
- /// <summary>
- /// Checks if user is successfully authenticated when user is redirected back to this user.
- /// </summary>
- /// <returns>The result of the authentication.</returns>
- public AuthenticationResult VerifyAuthentication() {
- AuthenticationResult result = this.authenticationProvider.VerifyAuthentication(this.requestContext);
- 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);
+ // 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("N");
+ uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId);
+
+ var xsrfCookie = new HttpCookie(SessionIdCookieName, sessionId) {
+ HttpOnly = true
+ };
+ if (FormsAuthentication.RequireSSL) {
+ xsrfCookie.Secure = true;
}
+ this.requestContext.Response.Cookies.Add(xsrfCookie);
- return result;
+ // issue the redirect to the external auth provider
+ this.authenticationProvider.RequestAuthentication(this.requestContext, uri);
}
/// <summary>
/// Checks if user is successfully authenticated when user is redirected back to this user.
/// </summary>
/// <param name="returnUrl">The return Url which must match exactly the Url passed into RequestAuthentication() earlier.</param>
+ /// <remarks>
+ /// 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
+ string sessionId;
+ bool successful = this.ValidateRequestAgainstXsrfAttack(out sessionId);
+ 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;
@@ -191,24 +213,69 @@ namespace DotNetOpenAuth.AspNet {
uri = this.requestContext.Request.GetPublicFacingUrl();
}
- 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);
- }
+ // 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);
+
+ // When we called RequestAuthentication(), we put the sessionId in the returnUrl query string.
+ // Hence, we need to put it in the VerifyAuthentication url again to please FB/Microsoft account providers.
+ uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId);
- return result;
+ 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>
+ /// Validates the request against XSRF attack.
+ /// </summary>
+ /// <param name="sessionId">The session id embedded in the query string.</param>
+ /// <returns>
+ /// <c>true</c> if the request is safe. Otherwise, <c>false</c>.
+ /// </returns>
+ private bool ValidateRequestAgainstXsrfAttack(out string sessionId) {
+ // get the session id query string parameter
+ string queryStringSessionId = this.requestContext.Request.QueryString[SessionIdQueryStringName];
+
+ // verify that the query string value is a valid guid
+ Guid guid;
+ if (!Guid.TryParse(queryStringSessionId, out guid)) {
+ sessionId = null;
+ return false;
+ }
+
+ // get the cookie id query string parameter
+ var cookie = this.requestContext.Request.Cookies[SessionIdCookieName];
+
+ bool successful = cookie != null && queryStringSessionId == cookie.Value;
+
+ if (successful) {
+ // be a good citizen, clean up cookie when the authentication succeeds
+ this.requestContext.Response.Cookies.Remove(SessionIdCookieName);
+ }
+
+ sessionId = queryStringSessionId;
+ return successful;
+ }
+
#endregion
}
} \ No newline at end of file